Module: Familia::Features::ExternalIdentifier

Defined in:
lib/familia/features/external_identifier.rb

Overview

Familia::Features::ExternalIdentifier

Defined Under Namespace

Modules: ClassMethods Classes: ExternalIdentifierError, ExternalIdentifierFieldType

Instance Method Summary collapse

Instance Method Details

#derive_external_identifierString?

Derives a deterministic, public-facing external identifier from the object’s internal objid.

This method uses the objid’s high-quality randomness to seed a pseudorandom number generator (PRNG). The PRNG then acts as a complex, deterministic function to produce a new identifier that has no discernible mathematical correlation to the objid. This is a security measure to prevent leaking information (like timestamps from UUIDv7) from the internal identifier to the public one.

The resulting identifier is always deterministic: the same objid will always produce the same extid, which is crucial for lookups.

Returns:

  • (String, nil)

    A prefixed, base36-encoded external identifier, or nil if the objid is not present.

Raises:



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/familia/features/external_identifier.rb', line 180

def derive_external_identifier
  raise ExternalIdentifierError, 'Missing objid field' unless respond_to?(:objid)

  current_objid = objid
  return nil if current_objid.nil? || current_objid.to_s.empty?

  # Validate objid provenance for security guarantees
  validate_objid_provenance!

  # Normalize the objid to a consistent hex representation first.
  normalized_hex = normalize_objid_to_hex(current_objid)

  # Use the objid's randomness to create a deterministic, yet secure,
  # external identifier. We do not use SecureRandom here because the output
  # must be deterministic.
  #
  # The process is as follows:
  # 1. The objid (a high-entropy value) is hashed to create a uniform seed.
  # 2. The seed initializes a standard PRNG (Random.new).
  # 3. The PRNG acts as a deterministic function to generate a sequence of
  #    bytes that appears random, obscuring the original objid.

  # 1. Create a high-quality, uniform seed from the objid's entropy.
  seed = Digest::SHA256.digest(normalized_hex)

  # 2. Initialize a PRNG with the seed. The same seed will always produce
  #    the same sequence of "random" numbers.
  prng = Random.new(seed.unpack1('Q>'))

  # 3. Generate 16 bytes (128 bits) of deterministic output.
  random_bytes = prng.bytes(16)

  # Encode as a base36 string for a compact, URL-safe identifier.
  # 128 bits is approximately 25 characters in base36.
  external_part = random_bytes.unpack1('H*').to_i(16).to_s(36).rjust(25, '0')

  # Get prefix from feature options, default to "ext"
  options = self.class.feature_options(:external_identifier)
  prefix = options[:prefix] || 'ext'

  "#{prefix}_#{external_part}"
end

#destroy!Object



239
240
241
242
243
244
245
# File 'lib/familia/features/external_identifier.rb', line 239

def destroy!
  # Clean up extid mapping when object is destroyed
  current_extid = instance_variable_get(:@extid)
  self.class.extid_lookup.remove_field(current_extid) if current_extid

  super if defined?(super)
end

#external_identifierString

Full-length alias for extid for clarity when needed

Returns:

  • (String)

    The external identifier



227
228
229
# File 'lib/familia/features/external_identifier.rb', line 227

def external_identifier
  extid
end

#external_identifier=(value) ⇒ Object

Full-length alias setter for extid

Parameters:

  • value (String)

    The external identifier to set



235
236
237
# File 'lib/familia/features/external_identifier.rb', line 235

def external_identifier=(value)
  self.extid = value
end