Skip to content

Commit

Permalink
Merge pull request #126 from danielberkompas/ensure-vault-started
Browse files Browse the repository at this point in the history
Raise helpful error if vault hasn't been started
  • Loading branch information
danielberkompas authored Apr 6, 2024
2 parents 565747e + 267077e commit ed545fa
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 28 deletions.
50 changes: 50 additions & 0 deletions lib/cloak/exceptions/vault_not_started.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
defmodule Cloak.VaultNotStarted do
@moduledoc """
This exception indicates that your vault process hasn't been started yet. Before
calling any vault functions, you must either:
#### 1. Call `start_link/0` on your vault module
MyApp.Vault.start_link()
#### 2. Ensure your application is started
If your vault module has been added to your application supervision tree, make
sure your application is running before calling any vault functions.
Application.ensure_all_started(:my_app)
"""
defexception [:message, :vault]

@doc false
def exception(table_name) do
vault = infer_vault_name(table_name)

%__MODULE__{
message: """
#{vault}.Config ETS table was not found!
This indicates that your vault process is not running. Ensure that it is
running before calling this function.
The simplest way to do that is to call `start_link/0`:
#{vault}.start_link()
If your vault has been added to your application's supervision tree,
ensure that your app has been started before calling any vault functions.
Application.ensure_all_started(:my_app)
""",
vault: vault
}
end

defp infer_vault_name(table_name) do
table_name
|> to_string()
|> String.replace(".Config", "")
|> String.replace("Elixir.", "")
|> String.to_atom()
end
end
66 changes: 38 additions & 28 deletions lib/cloak/vault.ex
Original file line number Diff line number Diff line change
Expand Up @@ -222,51 +222,63 @@ defmodule Cloak.Vault do

@impl Cloak.Vault
def encrypt(plaintext) do
@table_name
|> Cloak.Vault.read_config()
|> Cloak.Vault.encrypt(plaintext)
with {:ok, config} <- Cloak.Vault.read_config(@table_name) do
Cloak.Vault.encrypt(config, plaintext)
end
end

@impl Cloak.Vault
def encrypt!(plaintext) do
@table_name
|> Cloak.Vault.read_config()
|> Cloak.Vault.encrypt!(plaintext)
case Cloak.Vault.read_config(@table_name) do
{:ok, config} ->
Cloak.Vault.encrypt!(config, plaintext)

{:error, error} ->
raise error
end
end

@impl Cloak.Vault
def encrypt(plaintext, label) do
@table_name
|> Cloak.Vault.read_config()
|> Cloak.Vault.encrypt(plaintext, label)
with {:ok, config} <- Cloak.Vault.read_config(@table_name) do
Cloak.Vault.encrypt(config, plaintext, label)
end
end

@impl Cloak.Vault
def encrypt!(plaintext, label) do
@table_name
|> Cloak.Vault.read_config()
|> Cloak.Vault.encrypt!(plaintext, label)
case Cloak.Vault.read_config(@table_name) do
{:ok, config} ->
Cloak.Vault.encrypt!(config, plaintext, label)

{:error, error} ->
raise error
end
end

@impl Cloak.Vault
def decrypt(ciphertext) do
@table_name
|> Cloak.Vault.read_config()
|> Cloak.Vault.decrypt(ciphertext)
with {:ok, config} <- Cloak.Vault.read_config(@table_name) do
Cloak.Vault.decrypt(config, ciphertext)
end
end

@impl Cloak.Vault
def decrypt!(ciphertext) do
@table_name
|> Cloak.Vault.read_config()
|> Cloak.Vault.decrypt!(ciphertext)
case Cloak.Vault.read_config(@table_name) do
{:ok, config} ->
Cloak.Vault.decrypt!(config, ciphertext)

{:error, error} ->
raise error
end
end

@impl Cloak.Vault
def json_library do
@table_name
|> Cloak.Vault.read_config()
|> Keyword.get(:json_library, Jason)
with {:ok, config} <- Cloak.Vault.read_config(@table_name) do
Keyword.get(config, :json_library, Jason)
end
end

defoverridable(Module.definitions_in(__MODULE__))
Expand All @@ -284,13 +296,11 @@ defmodule Cloak.Vault do

@doc false
def read_config(table_name) do
case :ets.lookup(table_name, :config) do
[{:config, config} | _] ->
config

_ ->
:error
end
[{:config, config} | _] = :ets.lookup(table_name, :config)
{:ok, config}
rescue
ArgumentError ->
{:error, Cloak.VaultNotStarted.exception(table_name)}
end

@doc false
Expand Down
34 changes: 34 additions & 0 deletions test/cloak/vault_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ defmodule Cloak.VaultTest do

GenServer.stop(pid)
end

test "returns helpful error if vault hasn't been started" do
assert {:error, %Cloak.VaultNotStarted{}} = RuntimeVault.encrypt("plaintext")
end
end

describe ".encrypt!/1" do
Expand All @@ -105,6 +109,12 @@ defmodule Cloak.VaultTest do

GenServer.stop(pid)
end

test "raises error if vault has not been started" do
assert_raise Cloak.VaultNotStarted, fn ->
RuntimeVault.encrypt!("plaintext")
end
end
end

describe ".encrypt/2" do
Expand All @@ -116,6 +126,10 @@ defmodule Cloak.VaultTest do
test "returns error if no cipher associated with label" do
assert {:error, %Cloak.MissingCipher{}} = TestVault.encrypt("plaintext", :nonexistent)
end

test "returns error if vault has not been started" do
assert {:error, %Cloak.VaultNotStarted{}} = RuntimeVault.encrypt("plaintext", :secondary)
end
end

describe ".encrypt!/2" do
Expand All @@ -130,6 +144,12 @@ defmodule Cloak.VaultTest do
TestVault.encrypt!("plaintext", :nonexistent)
end
end

test "raises error if vault has not been started" do
assert_raise Cloak.VaultNotStarted, fn ->
RuntimeVault.encrypt!("plaintext", :secondary)
end
end
end

describe ".decrypt/1" do
Expand All @@ -144,6 +164,10 @@ defmodule Cloak.VaultTest do
test "returns error if no module found to decrypt" do
assert {:error, %Cloak.MissingCipher{}} = TestVault.decrypt(<<123, 123>>)
end

test "returns error if vault not started" do
assert {:error, %Cloak.VaultNotStarted{}} = RuntimeVault.decrypt(<<123, 123>>)
end
end

describe ".decrypt!" do
Expand All @@ -160,11 +184,21 @@ defmodule Cloak.VaultTest do
TestVault.decrypt!(<<123, 123>>)
end
end

test "raises error if vault has not been started" do
assert_raise Cloak.VaultNotStarted, fn ->
RuntimeVault.decrypt!(<<123, 123>>)
end
end
end

describe ".json_library/1" do
test "returns Jason by default" do
assert TestVault.json_library() == Jason
end

test "returns error if vault has not been started" do
assert {:error, %Cloak.VaultNotStarted{}} = RuntimeVault.json_library()
end
end
end

0 comments on commit ed545fa

Please sign in to comment.