mirror of
https://github.com/teslamate-org/teslamate.git
synced 2026-01-24 21:06:08 +08:00
Store API tokens encrypted in the database. During the database migration a randomly generated key will be used encrypt the tokens if no ENCRYPTION_KEY environment variable was provided. If the application is started without the presence of an ENCRYPTION_KEY (or if the key failed to decrypt the existing tokens), the UI will display a warning with further instructions.
124 lines
3.1 KiB
Elixir
124 lines
3.1 KiB
Elixir
defmodule TeslaMate.Repo.Migrations.EncryptApiTokens do
|
|
use Ecto.Migration
|
|
|
|
Code.ensure_loaded!(TeslaMate.Vault)
|
|
|
|
defmodule Encrypted.Binary do
|
|
use Cloak.Ecto.Binary, vault: TeslaMate.Vault
|
|
end
|
|
|
|
defmodule Tokens do
|
|
use Ecto.Schema
|
|
|
|
schema "tokens" do
|
|
field(:refresh, :string)
|
|
field(:access, :string)
|
|
|
|
field(:encrypted_refresh, Encrypted.Binary)
|
|
field(:encrypted_access, Encrypted.Binary)
|
|
end
|
|
end
|
|
|
|
defmodule Encryption do
|
|
def setup do
|
|
{type, key} =
|
|
case System.get_env("ENCRYPTION_KEY") do
|
|
key when is_binary(key) and byte_size(key) > 0 -> {:existing, key}
|
|
_ -> {:generated, generate_key(64)}
|
|
end
|
|
|
|
setup_vault(key)
|
|
|
|
{type, key}
|
|
end
|
|
|
|
defp generate_key(length) when length > 31 do
|
|
:crypto.strong_rand_bytes(length) |> Base.encode64(padding: false) |> binary_part(0, length)
|
|
end
|
|
|
|
defp setup_vault(key) do
|
|
Cloak.Vault.save_config(TeslaMate.Vault.Config,
|
|
ciphers: [
|
|
default: TeslaMate.Vault.default_chipher(:crypto.hash(:sha256, key))
|
|
]
|
|
)
|
|
end
|
|
end
|
|
|
|
defmodule Cache do
|
|
require Logger
|
|
|
|
def store(encryption_key) do
|
|
Enum.each([System.tmp_dir(), import_dir()], fn dir ->
|
|
with dir when is_binary(dir) <- dir,
|
|
path = Path.join(dir, "tm_encryption.key"),
|
|
:ok <- File.write(path, encryption_key) do
|
|
Logger.info("Wrote encryption key to #{path}")
|
|
end
|
|
end)
|
|
end
|
|
|
|
defp import_dir do
|
|
path =
|
|
System.get_env("IMPORT_DIR", "import")
|
|
|> Path.absname()
|
|
|
|
if File.exists?(path), do: path
|
|
end
|
|
end
|
|
|
|
alias TeslaMate.Repo
|
|
|
|
def change do
|
|
alter table(:tokens) do
|
|
add :encrypted_refresh, :binary
|
|
add :encrypted_access, :binary
|
|
end
|
|
|
|
flush()
|
|
|
|
with [_ | _] = tokens <- Repo.all(Tokens) do
|
|
with {:generated, encryption_key} <- Encryption.setup() do
|
|
require Logger
|
|
|
|
Logger.warning("""
|
|
\n------------------------------------------------------------------------------
|
|
No ENCRYPTION_KEY was found to encrypt and securly store your API tokens.
|
|
|
|
Therefore, the following randomly generated key will be used instead:
|
|
|
|
|
|
#{encryption_key}
|
|
|
|
|
|
IMPORTANT: Create an environment variable named "ENCRYPTION_KEY" with the value
|
|
set to the key above and pass it to the application from now on.
|
|
|
|
If you choose to use a different key, a new login with your API tokens will be
|
|
required once after starting the application.
|
|
------------------------------------------------------------------------------
|
|
""")
|
|
|
|
Cache.store(encryption_key)
|
|
end
|
|
|
|
Enum.each(tokens, fn %Tokens{} = tokens ->
|
|
tokens
|
|
|> Ecto.Changeset.change(%{
|
|
encrypted_access: tokens.access,
|
|
encrypted_refresh: tokens.refresh
|
|
})
|
|
|> Repo.update!()
|
|
end)
|
|
end
|
|
|
|
alter table(:tokens) do
|
|
remove :access
|
|
remove :refresh
|
|
end
|
|
|
|
rename table(:tokens), :encrypted_access, to: :access
|
|
rename table(:tokens), :encrypted_refresh, to: :refresh
|
|
end
|
|
end
|