diff --git a/config/config.exs b/config/config.exs index 14ef29942..f57503a5c 100644 --- a/config/config.exs +++ b/config/config.exs @@ -41,22 +41,4 @@ config :plausible, :user_agent_cache, limit: 1000, stats: false -config :kaffy, - otp_app: :plausible, - ecto_repo: Plausible.Repo, - router: PlausibleWeb.Router, - admin_title: "Plausible Admin", - resources: [ - auth: [ - resources: [ - user: [schema: Plausible.Auth.User, admin: Plausible.Auth.UserAdmin] - ] - ], - sites: [ - resources: [ - site: [schema: Plausible.Site, admin: Plausible.SiteAdmin] - ] - ] - ] - import_config "#{config_env()}.exs" diff --git a/config/runtime.exs b/config/runtime.exs index b04472112..330da9d50 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -279,7 +279,8 @@ config :kaffy, resources: [ auth: [ resources: [ - user: [schema: Plausible.Auth.User, admin: Plausible.Auth.UserAdmin] + user: [schema: Plausible.Auth.User, admin: Plausible.Auth.UserAdmin], + api_key: [schema: Plausible.Auth.ApiKey, admin: Plausible.Auth.ApiKeyAdmin] ] ], sites: [ diff --git a/lib/plausible/auth/api_key.ex b/lib/plausible/auth/api_key.ex index 594d70cb5..2b3527c02 100644 --- a/lib/plausible/auth/api_key.ex +++ b/lib/plausible/auth/api_key.ex @@ -2,12 +2,12 @@ defmodule Plausible.Auth.ApiKey do use Ecto.Schema import Ecto.Changeset - @required [:user_id, :key, :name] - @optional [:scopes, :hourly_request_limit] + @required [:user_id, :name] + @optional [:key, :scopes, :hourly_request_limit] schema "api_keys" do field :name, :string field :scopes, {:array, :string}, default: ["stats:read:*"] - field :hourly_request_limit, :integer + field :hourly_request_limit, :integer, default: 1000 field :key, :string, virtual: true field :key_hash, :string @@ -22,7 +22,14 @@ defmodule Plausible.Auth.ApiKey do schema |> cast(attrs, @required ++ @optional) |> validate_required(@required) - |> process_key + |> generate_key() + |> process_key() + end + + def update(schema, attrs \\ %{}) do + schema + |> cast(attrs, [:name, :user_id, :scopes, :hourly_request_limit]) + |> validate_required([:user_id, :name]) end def do_hash(key) do @@ -42,6 +49,15 @@ defmodule Plausible.Auth.ApiKey do def process_key(changeset), do: changeset + defp generate_key(changeset) do + if !changeset.changes[:key] do + key = :crypto.strong_rand_bytes(64) |> Base.url_encode64() |> binary_part(0, 64) + change(changeset, key: key) + else + changeset + end + end + defp secret_key_base() do Application.get_env(:plausible, PlausibleWeb.Endpoint) |> Keyword.fetch!(:secret_key_base) diff --git a/lib/plausible/auth/api_key_admin.ex b/lib/plausible/auth/api_key_admin.ex new file mode 100644 index 000000000..8886aab11 --- /dev/null +++ b/lib/plausible/auth/api_key_admin.ex @@ -0,0 +1,46 @@ +defmodule Plausible.Auth.ApiKeyAdmin do + use Plausible.Repo + + def search_fields(_schema) do + [ + :name, + user: [:name, :email] + ] + end + + def custom_index_query(_conn, _schema, query) do + from(r in query, preload: [:user]) + end + + def create_changeset(schema, attrs) do + scopes = [attrs["scope"]] + Plausible.Auth.ApiKey.changeset(schema, Map.merge(%{"scopes" => scopes}, attrs)) + end + + def update_changeset(schema, attrs) do + Plausible.Auth.ApiKey.update(schema, attrs) |> IO.inspect() + end + + @plaintext_key_help """ + The value of the API key is sensitive data like a password. Once created, the value of they will never be revealed again. Make sure to copy/paste this into a secure place before hitting 'save'. When sending the key to a customer, use a secure E2EE system that destructs the message after a certain period like https://bitwarden.com/products/send + """ + def form_fields(_) do + [ + name: nil, + key: %{create: :readonly, update: :hidden, help_text: @plaintext_key_help}, + key_prefix: %{create: :hidden, update: :readonly}, + hourly_request_limit: %{default: 1000}, + scope: %{choices: [{"Stats API", ["stats:read:*"]}, {"Sites API", ["sites:provision:*"]}]}, + user_id: nil + ] + end + + def index(_) do + [ + key_prefix: nil, + name: nil, + scopes: nil, + owner: %{value: & &1.user.email} + ] + end +end