add encrypted map ecto type for integration credentials

This commit is contained in:
Graham McIntire 2026-02-12 16:52:05 -06:00
parent a61abd836c
commit 2fd195e6b3
No known key found for this signature in database
2 changed files with 63 additions and 0 deletions

View file

@ -0,0 +1,21 @@
defmodule Towerops.Encrypted.Map do
@moduledoc """
Encrypted map field using Cloak vault.
Used for storing sensitive configuration like API credentials as encrypted JSON.
Data is encrypted at rest using AES-256-GCM.
## Usage
field :credentials, Towerops.Encrypted.Map
## Database Schema
The database column must be `:binary`:
add :credentials, :binary
Note: Atom keys become string keys after decryption.
"""
use Cloak.Ecto.Map, vault: Towerops.Vault
end

View file

@ -0,0 +1,42 @@
defmodule Towerops.Encrypted.MapTest do
use Towerops.DataCase, async: true
alias Towerops.Encrypted.Map, as: EncryptedMap
describe "cast/1" do
test "casts a valid map" do
assert {:ok, %{"key" => "value"}} = EncryptedMap.cast(%{"key" => "value"})
end
test "casts atom-keyed maps" do
assert {:ok, %{key: "value"}} = EncryptedMap.cast(%{key: "value"})
end
test "rejects non-map values" do
assert :error = EncryptedMap.cast("not a map")
assert :error = EncryptedMap.cast(123)
end
test "casts nil" do
assert {:ok, nil} = EncryptedMap.cast(nil)
end
end
describe "dump/1 and load/1 round-trip" do
test "encrypts and decrypts a map" do
original = %{"api_key" => "secret123", "base_url" => "https://api.preseem.com"}
assert {:ok, encrypted} = EncryptedMap.dump(original)
assert is_binary(encrypted)
refute encrypted == Jason.encode!(original)
assert {:ok, decrypted} = EncryptedMap.load(encrypted)
assert decrypted == original
end
test "handles nil values" do
assert {:ok, nil} = EncryptedMap.dump(nil)
assert {:ok, nil} = EncryptedMap.load(nil)
end
end
end