Class: Otto::Privacy::Config

Inherits:
Object
  • Object
show all
Includes:
Core::Freezable
Defined in:
lib/otto/privacy/config.rb

Overview

Configuration for IP privacy features

Privacy is ENABLED by default for public IPs. Private/localhost IPs are not masked.

Examples:

Default configuration (privacy enabled)

config = Otto::Privacy::Config.new
config.enabled? # => true

Configure masking level

config = Otto::Privacy::Config.new
config.octet_precision = 2  # Mask 2 octets instead of 1

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Core::Freezable

#deep_freeze!

Constructor Details

#initialize(options = {}) ⇒ Config

Initialize privacy configuration

Parameters:

  • options (Hash) (defaults to: {})

    Configuration options

Options Hash (options):

  • :octet_precision (Integer)

    Number of trailing octets to mask (1 or 2, default: 1)

  • :hash_rotation_period (Integer)

    Seconds between key rotation (default: 86400)

  • :geo_enabled (Boolean)

    Enable geo-location resolution (default: true)

  • :disabled (Boolean)

    Disable privacy entirely (default: false)

  • :mask_private_ips (Boolean)

    Mask private/localhost IPs (default: false)

  • :correlation_secret (String)

    A secret string that turns on IP correlation. Default nil, meaning off.

    It answers one question: “are these two requests, maybe months apart, from the same visitor?” — without your app ever seeing the real IP.

    Otto masks each IP before your app runs (203.0.113.42 becomes 203.0.113.0), which is too coarse to tell visitors apart. When a secret is set, Otto also fingerprints the full IP, before masking, and hands your app just the fingerprint as req.ip_correlation_hash. The same IP always produces the same fingerprint, and it can’t be turned back into an IP without the secret.

    Keep the secret stable — changing it changes every fingerprint. An empty string is rejected, because an empty secret would let anyone reverse the fingerprint back to an IP.

  • :redis (Redis)

    Optional Redis connection for multi-server environments



71
72
73
74
75
76
77
78
79
# File 'lib/otto/privacy/config.rb', line 71

def initialize(options = {})
  @octet_precision = options.fetch(:octet_precision, 1)
  @hash_rotation_period = options.fetch(:hash_rotation_period, 86_400) # 24 hours
  @geo_enabled = options.fetch(:geo_enabled, true)
  @disabled = options.fetch(:disabled, false) # Enabled by default (privacy-by-default)
  @mask_private_ips = options.fetch(:mask_private_ips, false) # Don't mask private/localhost by default
  self.correlation_secret = options.fetch(:correlation_secret, nil) # Opt-in stable IP-correlation secret
  @redis = options[:redis] # Optional Redis connection for multi-server environments
end

Instance Attribute Details

#correlation_secretObject

Returns the value of attribute correlation_secret.



31
32
33
# File 'lib/otto/privacy/config.rb', line 31

def correlation_secret
  @correlation_secret
end

#disabledObject (readonly)

Returns the value of attribute disabled.



31
32
33
# File 'lib/otto/privacy/config.rb', line 31

def disabled
  @disabled
end

#geo_enabledObject

Returns the value of attribute geo_enabled.



30
31
32
# File 'lib/otto/privacy/config.rb', line 30

def geo_enabled
  @geo_enabled
end

#hash_rotation_periodObject

Returns the value of attribute hash_rotation_period.



30
31
32
# File 'lib/otto/privacy/config.rb', line 30

def hash_rotation_period
  @hash_rotation_period
end

#mask_private_ipsObject

Returns the value of attribute mask_private_ips.



30
31
32
# File 'lib/otto/privacy/config.rb', line 30

def mask_private_ips
  @mask_private_ips
end

#octet_precisionObject

Returns the value of attribute octet_precision.



30
31
32
# File 'lib/otto/privacy/config.rb', line 30

def octet_precision
  @octet_precision
end

Class Method Details

.rotation_keys_storeConcurrent::Map

Get the class-level rotation keys store

Returns:

  • (Concurrent::Map)

    Thread-safe map for rotation keys



40
41
42
43
# File 'lib/otto/privacy/config.rb', line 40

def rotation_keys_store
  @rotation_keys_store = Concurrent::Map.new unless defined?(@rotation_keys_store) && @rotation_keys_store
  @rotation_keys_store
end

Instance Method Details

#disable!self

Disable privacy (allows access to original IPs)

IMPORTANT: This should only be used when you have a specific requirement to access original IP addresses. By default, Otto provides privacy-safe masked IPs.

Returns:

  • (self)


121
122
123
124
# File 'lib/otto/privacy/config.rb', line 121

def disable!
  @disabled = true
  self
end

#disabled?Boolean

Check if privacy is disabled

Returns:

  • (Boolean)

    true if privacy was explicitly disabled



110
111
112
# File 'lib/otto/privacy/config.rb', line 110

def disabled?
  @disabled
end

#enable!self

Enable privacy (default state)

Returns:

  • (self)


129
130
131
132
# File 'lib/otto/privacy/config.rb', line 129

def enable!
  @disabled = false
  self
end

#enabled?Boolean

Check if privacy is enabled

Returns:

  • (Boolean)

    true if privacy is enabled (default)



103
104
105
# File 'lib/otto/privacy/config.rb', line 103

def enabled?
  !@disabled
end

#rotation_keyString

Get the current rotation key for IP hashing

Keys rotate at fixed intervals based on hash_rotation_period (default: 24 hours). Each rotation period gets a unique key, ensuring IP addresses hash differently across periods while remaining consistent within.

Multi-server support: - With Redis: Uses SET NX GET EX for atomic key generation across all servers - Without Redis: Falls back to in-memory Concurrent::Hash (single-server only)

Redis keys: - rotation_key:timestamp - Stores the rotation key with TTL

Returns:

  • (String)

    Current rotation key for hashing



148
149
150
151
152
153
154
# File 'lib/otto/privacy/config.rb', line 148

def rotation_key
  if @redis
    rotation_key_redis
  else
    rotation_key_memory
  end
end

#validate!Object

Validate configuration settings

Raises:

  • (ArgumentError)

    if configuration is invalid



159
160
161
162
163
164
165
166
# File 'lib/otto/privacy/config.rb', line 159

def validate!
  raise ArgumentError, "octet_precision must be 1 or 2, got: #{@octet_precision}" unless [1,
                                                                                          2].include?(@octet_precision)

  return unless @hash_rotation_period < 60

  raise ArgumentError, 'hash_rotation_period must be at least 60 seconds'
end