wiki/lib/wiki/content/page.ex

83 lines
2.0 KiB
Elixir

defmodule Wiki.Content.Page do
use Ecto.Schema
import Ecto.Changeset
schema "pages" do
field :encrypted_content, :binary
field :encrypted_content_iv, :binary
field :encrypted_content_tag, :binary
field :content, :string, virtual: true
field :content_encryption_key, :string, virtual: true
field :title, :string
belongs_to :user, Wiki.Accounts.User
timestamps()
end
@doc false
def changeset(page, attrs) do
page
|> cast(attrs, [
:title,
:content,
:content_encryption_key,
:user_id
])
|> encrypt_changeset()
|> validate_required([
:title,
:encrypted_content,
:user_id
])
end
defp encrypt_changeset(%Ecto.Changeset{changes: %{content: _}} = changeset) do
content = get_change(changeset, :content)
key = get_field(changeset, :content_encryption_key)
{encrypted_content, tag, iv} = do_encrypt(content, key)
changeset
|> put_change(:encrypted_content, encrypted_content)
|> put_change(:encrypted_content_tag, tag)
|> put_change(:encrypted_content_iv, iv)
|> delete_change(:content)
|> delete_change(:content_encryption_key)
end
defp encrypt_changeset(changeset), do: changeset
@iv_size 16
defp do_encrypt(text, key) do
# key is a base16 encoded string (comes from Argon2.Base.hash_password w/ the format: :raw_hash option)
key = Base.decode16!(key, case: :lower)
iv = :crypto.strong_rand_bytes(@iv_size)
{encrypted_text, tag} =
:crypto.crypto_one_time_aead(:aes_256_gcm, key, iv, text, <<>>, true)
|> IO.inspect()
{encrypted_text, tag, iv}
end
def decrypt_content(page) do
key = Base.decode16!(page.content_encryption_key, case: :lower)
iv = page.encrypted_content_iv
tag = page.encrypted_content_tag
content =
:crypto.crypto_one_time_aead(
:aes_256_gcm,
key,
iv,
page.encrypted_content,
<<>>,
tag,
false
)
%__MODULE__{page | content: content}
end
end