encoded_ids 1.0.0 → 1.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 +4 -4
- data/CHANGELOG.md +21 -2
- data/README.md +37 -3
- data/examples/initializer.rb +18 -4
- data/examples/models.rb +20 -6
- data/lib/encoded_ids/hashid_identifiable.rb +9 -3
- data/lib/encoded_ids/railtie.rb +35 -3
- data/lib/encoded_ids/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc01cbbed877c526bd59169c2f74f183ef909fb2da4beecb9354ac0b48d0723a
|
|
4
|
+
data.tar.gz: ae607c87436672951ebed59c753a7e4c01ebd0f249a5a1d6c196c9b5ff6cdcb3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a203a1bf4c38e6652ad256aa668d319991be746b0b20e187ae3d9de26851650da8e7fb20dca7aecd0a4ee019d23be1ce402f8a83d9e5c9d3ea9fd66364be1ae
|
|
7
|
+
data.tar.gz: d9cea9e34f8b0e2816b0a509794b613fc47ae3159eb5c6add6f7b5e2fc789da51f7c88edb55b7ab52f65bb2d69c2204be5fc6c14fc0af31b8005c8af5bb0077d
|
data/CHANGELOG.md
CHANGED
|
@@ -5,12 +5,31 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.0] - 2026-02-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Per-model salt override support via `salt:` parameter in `set_public_id_prefix`
|
|
12
|
+
- Environment variable support (`ENV["HASHID_SALT"]`) in salt fallback chain
|
|
13
|
+
- Development warning when no custom salt is configured
|
|
14
|
+
- Enhanced security documentation in README
|
|
15
|
+
- Security examples in models.rb showing salt override patterns
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Improved salt fallback chain: config → credentials → ENV → secret_key_base
|
|
19
|
+
- Enhanced initializer example with better security guidance
|
|
20
|
+
- Updated documentation to emphasize importance of using a unique salt
|
|
21
|
+
|
|
22
|
+
### Security
|
|
23
|
+
- **IMPORTANT**: Without a unique salt, hash IDs can be calculated by anyone who knows your database IDs
|
|
24
|
+
- Per-model salts allow for better security isolation between models
|
|
25
|
+
- Empty salt option (`salt: ""`) available for backward compatibility with legacy systems
|
|
26
|
+
|
|
8
27
|
## [1.0.0] - 2026-02-09
|
|
9
28
|
|
|
10
29
|
### Added
|
|
11
30
|
- Initial stable release
|
|
12
|
-
- `
|
|
13
|
-
- `
|
|
31
|
+
- `HashidIdentifiable` concern for integer primary keys
|
|
32
|
+
- `UuidIdentifiable` concern for UUID primary keys
|
|
14
33
|
- Automatic `to_param` override for Rails URL generation
|
|
15
34
|
- Overridden `find` method to accept both internal and public IDs
|
|
16
35
|
- `find_by_public_id` and `find_by_public_id!` class methods
|
data/README.md
CHANGED
|
@@ -158,7 +158,8 @@ Create an initializer at `config/initializers/encoded_ids.rb`:
|
|
|
158
158
|
```ruby
|
|
159
159
|
EncodedIds.configure do |config|
|
|
160
160
|
# Hashid configuration (for integer IDs)
|
|
161
|
-
|
|
161
|
+
# SECURITY IMPORTANT: Set a unique salt! See Security section below.
|
|
162
|
+
config.hashid_salt = Rails.application.credentials.dig(:hashid, :salt) || ENV["HASHID_SALT"]
|
|
162
163
|
config.hashid_min_length = 8
|
|
163
164
|
config.hashid_alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
164
165
|
|
|
@@ -175,15 +176,48 @@ EncodedIds.configure do |config|
|
|
|
175
176
|
end
|
|
176
177
|
```
|
|
177
178
|
|
|
178
|
-
|
|
179
|
+
### Security: Configuring Your Salt
|
|
180
|
+
|
|
181
|
+
**IMPORTANT:** Without a unique salt, hash IDs can be calculated by anyone who knows your database IDs, making them only marginally better than exposing raw IDs.
|
|
182
|
+
|
|
183
|
+
The gem uses a fallback chain for the salt:
|
|
184
|
+
1. `EncodedIds.configuration.hashid_salt` (set in initializer)
|
|
185
|
+
2. `Rails.application.credentials.dig(:hashid, :salt)` (recommended)
|
|
186
|
+
3. `ENV["HASHID_SALT"]`
|
|
187
|
+
4. `Rails.application.secret_key_base` (not recommended - shows warning in development)
|
|
188
|
+
|
|
189
|
+
**Recommended setup:**
|
|
179
190
|
|
|
180
191
|
```bash
|
|
192
|
+
# Generate a unique salt
|
|
181
193
|
rails credentials:edit
|
|
182
194
|
```
|
|
183
195
|
|
|
196
|
+
Add to your credentials:
|
|
184
197
|
```yaml
|
|
185
198
|
hashid:
|
|
186
|
-
salt:
|
|
199
|
+
salt: <paste output of: SecureRandom.hex(32)> # e.g., "a1b2c3d4e5f6..."
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The initializer will automatically pick it up:
|
|
203
|
+
```ruby
|
|
204
|
+
config.hashid_salt = Rails.application.credentials.dig(:hashid, :salt)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Per-model salt override:**
|
|
208
|
+
|
|
209
|
+
For models that need different salts (e.g., to maintain compatibility with legacy IDs):
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
class LegacyUser < ApplicationRecord
|
|
213
|
+
include EncodedIds::HashidIdentifiable
|
|
214
|
+
set_public_id_prefix :usr, salt: "" # Empty salt for backward compatibility
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
class SecureDocument < ApplicationRecord
|
|
218
|
+
include EncodedIds::HashidIdentifiable
|
|
219
|
+
set_public_id_prefix :doc, salt: Rails.application.credentials.dig(:documents, :salt)
|
|
220
|
+
end
|
|
187
221
|
```
|
|
188
222
|
|
|
189
223
|
## How It Works
|
data/examples/initializer.rb
CHANGED
|
@@ -7,11 +7,25 @@
|
|
|
7
7
|
|
|
8
8
|
EncodedIds.configure do |config|
|
|
9
9
|
# Hashid configuration (for integer IDs)
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
|
|
10
|
+
#
|
|
11
|
+
# SECURITY IMPORTANT: Set a unique salt for your application!
|
|
12
|
+
# Without a salt, hash IDs can be calculated by anyone who knows your database IDs.
|
|
13
|
+
#
|
|
14
|
+
# Method 1 (Recommended): Store in encrypted credentials
|
|
15
|
+
# rails credentials:edit
|
|
16
|
+
# Add: hashid: { salt: "your-unique-salt-here" }
|
|
17
|
+
# Generate with: SecureRandom.hex(32)
|
|
18
|
+
#
|
|
19
|
+
# Method 2: Use environment variable
|
|
20
|
+
# config.hashid_salt = ENV["HASHID_SALT"]
|
|
21
|
+
#
|
|
22
|
+
# Method 3: Mix of both (credentials preferred, ENV as fallback)
|
|
23
|
+
config.hashid_salt = Rails.application.credentials.dig(:hashid, :salt) || ENV["HASHID_SALT"]
|
|
24
|
+
|
|
25
|
+
# Minimum length of the hash portion (before prefix)
|
|
14
26
|
config.hashid_min_length = 8
|
|
27
|
+
|
|
28
|
+
# Character set for hash generation (lowercase + numbers by default)
|
|
15
29
|
config.hashid_alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
16
30
|
|
|
17
31
|
# Base62 alphabet (for UUID encoding)
|
data/examples/models.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# Integer primary key with simple prefix
|
|
6
6
|
class User < ApplicationRecord
|
|
7
|
-
include EncodedIds::
|
|
7
|
+
include EncodedIds::HashidIdentifiable
|
|
8
8
|
set_public_id_prefix :usr
|
|
9
9
|
end
|
|
10
10
|
# user.public_id => "usr_k5qx9z"
|
|
@@ -12,14 +12,14 @@ end
|
|
|
12
12
|
|
|
13
13
|
# Integer primary key with longer hash for high-volume tables
|
|
14
14
|
class Event < ApplicationRecord
|
|
15
|
-
include EncodedIds::
|
|
15
|
+
include EncodedIds::HashidIdentifiable
|
|
16
16
|
set_public_id_prefix :evt, min_hash_length: 12
|
|
17
17
|
end
|
|
18
18
|
# event.public_id => "evt_x5qp9z2m8n4k"
|
|
19
19
|
|
|
20
20
|
# Integer primary key with compositional prefix
|
|
21
21
|
class Intel::Tool::PhoneNumber < ApplicationRecord
|
|
22
|
-
include EncodedIds::
|
|
22
|
+
include EncodedIds::HashidIdentifiable
|
|
23
23
|
add_public_id_segment :int
|
|
24
24
|
add_public_id_segment :tool
|
|
25
25
|
add_public_id_segment :phn
|
|
@@ -28,22 +28,36 @@ end
|
|
|
28
28
|
|
|
29
29
|
# UUID primary key
|
|
30
30
|
class Organization < ApplicationRecord
|
|
31
|
-
include EncodedIds::
|
|
31
|
+
include EncodedIds::UuidIdentifiable
|
|
32
32
|
set_public_id_prefix "org"
|
|
33
33
|
end
|
|
34
34
|
# org.public_id => "org_4k8xJm2pN9qW"
|
|
35
35
|
|
|
36
36
|
# UUID primary key with different prefix
|
|
37
37
|
class Team < ApplicationRecord
|
|
38
|
-
include EncodedIds::
|
|
38
|
+
include EncodedIds::UuidIdentifiable
|
|
39
39
|
set_public_id_prefix "team"
|
|
40
40
|
end
|
|
41
41
|
# team.public_id => "team_7n2kLp4xMq8R"
|
|
42
42
|
|
|
43
43
|
# Override per-model to include prefix in routes (Stripe style)
|
|
44
44
|
class ApiKey < ApplicationRecord
|
|
45
|
-
include EncodedIds::
|
|
45
|
+
include EncodedIds::HashidIdentifiable
|
|
46
46
|
set_public_id_prefix :key, use_prefix_in_routes: true
|
|
47
47
|
end
|
|
48
48
|
# api_key.public_id => "key_abc123"
|
|
49
49
|
# api_key.to_param => "key_abc123" (includes prefix)
|
|
50
|
+
|
|
51
|
+
# Security: Custom salt for specific model
|
|
52
|
+
class SecureDocument < ApplicationRecord
|
|
53
|
+
include EncodedIds::HashidIdentifiable
|
|
54
|
+
set_public_id_prefix :doc, salt: Rails.application.credentials.dig(:documents, :salt)
|
|
55
|
+
end
|
|
56
|
+
# Uses a different salt than the global default for added security
|
|
57
|
+
|
|
58
|
+
# Security: Empty salt for backward compatibility with legacy IDs
|
|
59
|
+
class LegacyUser < ApplicationRecord
|
|
60
|
+
include EncodedIds::HashidIdentifiable
|
|
61
|
+
set_public_id_prefix :leg, salt: ""
|
|
62
|
+
end
|
|
63
|
+
# Maintains compatibility with existing hash IDs generated without a salt
|
|
@@ -54,13 +54,19 @@ module EncodedIds
|
|
|
54
54
|
|
|
55
55
|
module ClassMethods
|
|
56
56
|
# Simple approach: set the full prefix directly
|
|
57
|
-
|
|
57
|
+
# Options:
|
|
58
|
+
# - min_hash_length: Minimum length of the encoded hash (default: 8)
|
|
59
|
+
# - use_prefix_in_routes: Include prefix in to_param URLs (default: nil, inherits from config)
|
|
60
|
+
# - salt: Custom salt for this model (default: nil, uses global config)
|
|
61
|
+
def set_public_id_prefix(prefix, min_hash_length: 8, use_prefix_in_routes: nil, salt: nil)
|
|
58
62
|
self.public_id_prefix = prefix.to_s.downcase
|
|
59
63
|
self.hashid_min_length = min_hash_length
|
|
60
64
|
self.use_prefix_in_routes = use_prefix_in_routes
|
|
61
65
|
|
|
62
|
-
# Configure hashid-rails for this model with custom length
|
|
63
|
-
|
|
66
|
+
# Configure hashid-rails for this model with custom length and optional salt
|
|
67
|
+
config_options = { min_hash_length: min_hash_length }
|
|
68
|
+
config_options[:salt] = salt if salt
|
|
69
|
+
hashid_config(config_options)
|
|
64
70
|
end
|
|
65
71
|
|
|
66
72
|
# Compositional approach: add segments that get joined with underscores
|
data/lib/encoded_ids/railtie.rb
CHANGED
|
@@ -5,9 +5,41 @@ module EncodedIds
|
|
|
5
5
|
initializer "encoded_ids.configure_hashid_rails" do
|
|
6
6
|
# Configure hashid-rails with gem settings
|
|
7
7
|
Hashid::Rails.configure do |config|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
# Project-wide salt fallback chain:
|
|
9
|
+
# 1. Explicitly configured via EncodedIds.configure
|
|
10
|
+
# 2. Rails credentials hashid.salt
|
|
11
|
+
# 3. ENV["HASHID_SALT"]
|
|
12
|
+
# 4. Rails secret_key_base (not recommended for production)
|
|
13
|
+
salt = EncodedIds.configuration.hashid_salt ||
|
|
14
|
+
Rails.application.credentials.dig(:hashid, :salt) ||
|
|
15
|
+
ENV["HASHID_SALT"] ||
|
|
16
|
+
Rails.application.secret_key_base
|
|
17
|
+
|
|
18
|
+
# Warn in development if using secret_key_base as salt
|
|
19
|
+
if Rails.env.development? &&
|
|
20
|
+
EncodedIds.configuration.hashid_salt.nil? &&
|
|
21
|
+
Rails.application.credentials.dig(:hashid, :salt).nil? &&
|
|
22
|
+
ENV["HASHID_SALT"].nil?
|
|
23
|
+
Rails.logger.warn <<~WARNING
|
|
24
|
+
[EncodedIds] No hashid_salt configured. Using secret_key_base as fallback.
|
|
25
|
+
|
|
26
|
+
For better security, set a unique salt in config/credentials.yml.enc:
|
|
27
|
+
hashid:
|
|
28
|
+
salt: #{SecureRandom.hex(32)}
|
|
29
|
+
|
|
30
|
+
Or via environment variable:
|
|
31
|
+
HASHID_SALT=#{SecureRandom.hex(32)}
|
|
32
|
+
|
|
33
|
+
Or in config/initializers/encoded_ids.rb:
|
|
34
|
+
EncodedIds.configure do |config|
|
|
35
|
+
config.hashid_salt = Rails.application.credentials.dig(:hashid, :salt) || ENV["HASHID_SALT"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Using the default salt makes hash IDs predictable if someone knows your model IDs.
|
|
39
|
+
WARNING
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
config.salt = salt
|
|
11
43
|
config.min_hash_length = EncodedIds.configuration.hashid_min_length
|
|
12
44
|
config.alphabet = EncodedIds.configuration.hashid_alphabet
|
|
13
45
|
end
|
data/lib/encoded_ids/version.rb
CHANGED