# `AshAuthentication.AddOn.AuditLog.IpPrivacy`
[🔗](https://github.com/team-alembic/ash_authentication/blob/main/lib/ash_authentication/add_ons/audit_log/ip_privacy.ex#L5)

Provides IP address privacy transformations for audit logging.

The audit log add-on transforms client IP addresses with this module before it
writes them. Select the transformation with the `ip_privacy_mode` option of the
`audit_log` add-on.

## Modes

- `:none` - store the address unchanged. This is the default.
- `:truncate` - keep the network prefix of the address and drop the host part.
- `:exclude` - do not store the address at all.
- `:hash` - store a keyed digest of the address.

## The `:hash` mode

`:hash` computes an HMAC-SHA256 of the address under a salt which you must
configure, then keeps the first 64 bits of the result:

```elixir
config :my_app, audit_log_ip_salt: System.fetch_env!("AUDIT_LOG_IP_SALT")
```

The salt can be a string, or a `{module, function, arguments}` tuple which
returns a string. The add-on reads it from the application which owns the
resource being audited, so each application in an umbrella has its own salt.

## Deprecated salt locations

Earlier versions read the salt from this library's own application name. Both
`config :ash_authentication, audit_log_ip_salt: ...` and
`config :ash_authentication, secret: ...` still work, and are used when the
owning application configures nothing. Both are deprecated, both warn at start
up, and both will be removed in a future release.

Run `mix ash_authentication.upgrade` to move the setting. Keep the value
identical: a different salt changes every stored digest, so entries written
before the move stop correlating with entries written after it.

The salt must be secret and it must have high entropy. IPv4 has only 2^32
addresses, so anybody who knows the salt can compute the digest of every
address and reverse the stored values. There is no salt which is safe to share
between deployments, and there is no safe default. `:hash` therefore fails
closed: `AshAuthentication.Supervisor` refuses to start when a resource selects
`:hash` without a salt, and `hash_ip/1` raises for the same reason.

A digest still identifies one address, which lets you count the events which
come from it. Use `:truncate` or `:exclude` when you do not need that.
Neither depends on a secret, so neither can fail in this way.

Hashing is not anonymisation. A digest of an IP address remains personal data
under the GDPR and similar laws, because it still singles out one subscriber.
Protect the stored digests the same way you would protect the raw addresses.

# `apply_privacy`

```elixir
@spec apply_privacy(String.t() | nil, atom(), map()) :: String.t() | nil
```

Apply privacy transformation to an IP address string.

## Options
- `:mode` - The privacy mode (`:none`, `:hash`, `:truncate`, `:exclude`)
- `:truncation_masks` - Map with `:ipv4` and `:ipv6` keys for truncation bits
- `:otp_app` - The application whose configuration holds the hash salt. Only
  used by `:hash`.

# `apply_to_request`

```elixir
@spec apply_to_request(map(), atom(), map()) :: map()
```

Apply privacy transformation to request data containing IP addresses.

Transforms the following fields:
- `remote_ip`
- `x_forwarded_for` (list of IPs)
- `forwarded` (list of forwarded headers)

# `hash_ip`

```elixir
@spec hash_ip(String.t(), atom() | nil) :: String.t() | nil
```

Hash an IP address with the configured salt.

Computes an HMAC-SHA256 of the address and keeps the first 64 bits of the
digest.

Raises when no salt is configured. See the module documentation for the reason
and for the configuration keys.

# `truncate_ip`

```elixir
@spec truncate_ip(String.t(), map()) :: String.t() | nil
```

Truncate an IP address to a network prefix.

For IPv4: Applies a subnet mask (e.g., /24 keeps first 3 octets)
For IPv6: Applies a prefix length (e.g., /48 keeps first 3 hextets)

# `verify_hash_salt!`

```elixir
@spec verify_hash_salt!(atom() | nil, [Ash.Resource.t()]) :: :ok
```

Check the given resources for an audit log add-on which hashes IP addresses
without a configured salt.

Raises when it finds one. `AshAuthentication.Supervisor` calls this when it
starts, so a deployment which is missing the salt fails to boot instead of
writing reversible digests.

Warns when the salt only resolves from the deprecated `:ash_authentication`
configuration.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
