Class: BetterAuth::SecretConfig
- Inherits:
-
Object
- Object
- BetterAuth::SecretConfig
- Defined in:
- lib/better_auth/secret_config.rb
Constant Summary collapse
- ENVELOPE_PREFIX =
"$ba$"
Instance Attribute Summary collapse
-
#current_version ⇒ Object
readonly
Returns the value of attribute current_version.
-
#keys ⇒ Object
readonly
Returns the value of attribute keys.
-
#legacy_secret ⇒ Object
readonly
Returns the value of attribute legacy_secret.
Class Method Summary collapse
- .build(secrets, legacy_secret, logger: nil) ⇒ Object
- .entropy(value) ⇒ Object
- .normalize_entry(entry) ⇒ Object
- .parse_env(value) ⇒ Object
- .parse_version!(value, source:) ⇒ Object
- .validate_secrets!(secrets, logger: nil) ⇒ Object
- .warn(logger, message) ⇒ Object
Instance Method Summary collapse
- #all_secrets ⇒ Object
- #current_secret ⇒ Object
-
#initialize(keys:, current_version:, legacy_secret: nil) ⇒ SecretConfig
constructor
A new instance of SecretConfig.
- #normalize_version!(version) ⇒ Object
Constructor Details
#initialize(keys:, current_version:, legacy_secret: nil) ⇒ SecretConfig
Returns a new instance of SecretConfig.
9 10 11 12 13 14 15 16 |
# File 'lib/better_auth/secret_config.rb', line 9 def initialize(keys:, current_version:, legacy_secret: nil) normalized_keys = keys.each_with_object({}) do |(version, value), result| result[normalize_version!(version)] = value.to_s end @keys = normalized_keys.freeze @current_version = normalize_version!(current_version) @legacy_secret = legacy_secret unless legacy_secret.to_s.empty? end |
Instance Attribute Details
#current_version ⇒ Object (readonly)
Returns the value of attribute current_version.
7 8 9 |
# File 'lib/better_auth/secret_config.rb', line 7 def current_version @current_version end |
#keys ⇒ Object (readonly)
Returns the value of attribute keys.
7 8 9 |
# File 'lib/better_auth/secret_config.rb', line 7 def keys @keys end |
#legacy_secret ⇒ Object (readonly)
Returns the value of attribute legacy_secret.
7 8 9 |
# File 'lib/better_auth/secret_config.rb', line 7 def legacy_secret @legacy_secret end |
Class Method Details
.build(secrets, legacy_secret, logger: nil) ⇒ Object
68 69 70 71 72 73 74 75 76 77 |
# File 'lib/better_auth/secret_config.rb', line 68 def self.build(secrets, legacy_secret, logger: nil) validate_secrets!(secrets, logger: logger) entries = Array(secrets).map { |entry| normalize_entry(entry) } keys = entries.each_with_object({}) do |entry, result| result[parse_version!(entry.fetch(:version), source: "`secrets`")] = entry.fetch(:value).to_s end current_version = parse_version!(entries.first.fetch(:version), source: "`secrets`") legacy = (legacy_secret && legacy_secret != Configuration::DEFAULT_SECRET) ? legacy_secret : nil new(keys: keys, current_version: current_version, legacy_secret: legacy) end |
.entropy(value) ⇒ Object
96 97 98 99 100 101 |
# File 'lib/better_auth/secret_config.rb', line 96 def self.entropy(value) unique = value.to_s.chars.uniq.length return 0 if unique.zero? Math.log2(unique**value.to_s.length) end |
.normalize_entry(entry) ⇒ Object
79 80 81 82 83 84 85 |
# File 'lib/better_auth/secret_config.rb', line 79 def self.normalize_entry(entry) raise Error, "Invalid `secrets` entry. Expected a hash with `version` and `value`." unless entry.is_a?(Hash) entry.each_with_object({}) do |(key, value), result| result[key.to_s.tr("-", "_").to_sym] = value end end |
.parse_env(value) ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/better_auth/secret_config.rb', line 30 def self.parse_env(value) return nil if value.to_s.empty? value.to_s.split(",").map do |entry| entry = entry.strip colon_index = entry.index(":") raise Error, "Invalid BETTER_AUTH_SECRETS entry: \"#{entry}\". Expected format: \"<version>:<secret>\"" unless colon_index version = entry[0...colon_index].strip secret = entry[(colon_index + 1)..].to_s.strip raise Error, "Empty secret value for version #{version} in BETTER_AUTH_SECRETS." if secret.empty? {version: parse_version!(version, source: "BETTER_AUTH_SECRETS"), value: secret} end end |
.parse_version!(value, source:) ⇒ Object
87 88 89 90 91 92 93 94 |
# File 'lib/better_auth/secret_config.rb', line 87 def self.parse_version!(value, source:) text = value.to_s.strip unless text.match?(/\A(?:0|[1-9]\d*)\z/) raise Error, "Invalid version #{value} in #{source}. Version must be a non-negative integer." end text.to_i end |
.validate_secrets!(secrets, logger: nil) ⇒ Object
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/better_auth/secret_config.rb', line 46 def self.validate_secrets!(secrets, logger: nil) entries = Array(secrets) raise Error, "`secrets` array must contain at least one entry." if entries.empty? seen = {} entries.each do |entry| data = normalize_entry(entry) version = parse_version!(data.fetch(:version), source: "`secrets`") value = data.fetch(:value, nil).to_s raise Error, "Empty secret value for version #{version} in `secrets`." if value.empty? raise Error, "Duplicate version #{version} in `secrets`. Each version must be unique." if seen[version] seen[version] = true end current = normalize_entry(entries.first) current_version = parse_version!(current.fetch(:version), source: "`secrets`") current_value = current.fetch(:value).to_s warn(logger, "[better-auth] Warning: the current secret (version #{current_version}) should be at least 32 characters long for adequate security.") if current_value.length < 32 warn(logger, "[better-auth] Warning: the current secret appears low-entropy. Use a randomly generated secret for production.") if entropy(current_value) < 120 end |
.warn(logger, message) ⇒ Object
103 104 105 106 107 108 109 |
# File 'lib/better_auth/secret_config.rb', line 103 def self.warn(logger, ) if logger.respond_to?(:call) logger.call(:warn, ) elsif logger.respond_to?(:warn) logger.warn() end end |
Instance Method Details
#all_secrets ⇒ Object
24 25 26 27 28 |
# File 'lib/better_auth/secret_config.rb', line 24 def all_secrets entries = keys.map { |version, value| [version, value] } entries << [-1, legacy_secret] if legacy_secret && !keys.value?(legacy_secret) entries end |
#current_secret ⇒ Object
18 19 20 21 22 |
# File 'lib/better_auth/secret_config.rb', line 18 def current_secret keys.fetch(current_version) do raise Error, "Secret version #{current_version} not found in keys" end end |
#normalize_version!(version) ⇒ Object
111 112 113 |
# File 'lib/better_auth/secret_config.rb', line 111 def normalize_version!(version) self.class.parse_version!(version, source: "`secrets`") end |