From a65e1a613d2b984c4e4fe63e7bdb692d3f561862 Mon Sep 17 00:00:00 2001 From: Inhji Date: Sun, 28 May 2023 22:36:13 +0200 Subject: [PATCH] add initial indieauth logic, add tesla --- lib/chiya_web/indie/indieauth.ex | 26 ++++++++++ lib/chiya_web/indie/token.ex | 88 ++++++++++++++++++++++++++++++++ mix.exs | 37 +++++++------- mix.lock | 1 + 4 files changed, 134 insertions(+), 18 deletions(-) create mode 100644 lib/chiya_web/indie/indieauth.ex create mode 100644 lib/chiya_web/indie/token.ex diff --git a/lib/chiya_web/indie/indieauth.ex b/lib/chiya_web/indie/indieauth.ex new file mode 100644 index 0000000..7728af6 --- /dev/null +++ b/lib/chiya_web/indie/indieauth.ex @@ -0,0 +1,26 @@ +defmodule ChiyaWeb.Indie.Auth do + use Tesla + require Logger + + @base_url Application.compile_env!(:chiya, [:indie, :token_endpoint]) + @user_agent Application.compile_env!(:chiya, [ChiyaWeb.Endpoint, :user_agent]) + + def verify_token(token) do + token + |> client() + |> get("") + end + + defp client(token) do + Tesla.client([ + {Tesla.Middleware.BaseUrl, @base_url}, + {Tesla.Middleware.JSON, [engine: Jason, engine_opts: [keys: :atoms]]}, + {Tesla.Middleware.Headers, + [ + {"User-Agent", @user_agent}, + {"Authorization", "Bearer #{token}"}, + {"Accept", "application/json"} + ]} + ]) + end +end diff --git a/lib/chiya_web/indie/token.ex b/lib/chiya_web/indie/token.ex new file mode 100644 index 0000000..760fc8f --- /dev/null +++ b/lib/chiya_web/indie/token.ex @@ -0,0 +1,88 @@ +defmodule ChiyaWeb.Indie.Token do + require Logger + + @supported_scopes Application.compile_env!(:chiya, [:indie, :supported_scopes]) + + def verify(access_token, required_scope, own_hostname) do + case ChiyaWeb.Indie.Auth.verify_token(access_token) do + {:ok, %{status: 200, body: body}} -> + verify_token_response(body, required_scope, own_hostname) + + {:ok, %{status: status}} -> + {:error, :insufficient_scope, status} + + {:error, %{code: code}} -> + Logger.error("Token endpoint responded with unexpected code: #{inspect(code)}") + {:error, :insufficient_scope, code} + + {:error, %{reason: reason}} -> + Logger.error("Could not reach token endpoint: #{inspect(reason)}") + {:error, :insufficient_scope, reason} + + error -> + Logger.error("Unexpected error: #{inspect(error)}") + {:error, :insufficient_scope, "Internal Server Error"} + end + end + + defp verify_token_response( + %{ + me: host_uri, + scope: scope, + client_id: client_id, + issued_at: _issued_at, + issued_by: _issued_by, + nonce: _nonce + }, + required_scope, + own_hostname + ) do + Logger.info("Host-URI: '#{host_uri}'") + Logger.info("ClientId: '#{client_id}'") + Logger.info("Scopes: '#{scope}'") + + with :ok <- verify_hostname_match(host_uri, own_hostname), + :ok <- verify_scope_support(scope, required_scope, @supported_scopes) do + :ok + else + {:error, name, reason} -> + Logger.error("Could not verify token response: #{reason}") + {:error, name, reason} + end + end + + defp verify_hostname_match(host_uri, own_hostname) do + hostnames_match? = get_hostname(host_uri) == own_hostname + + case hostnames_match? do + true -> + :ok + + _ -> + Logger.warn("Hostnames do not match: Given #{host_uri}, Actual: #{own_hostname}") + {:error, "verify_hostname_match", "hostname does not match"} + end + end + + defp get_hostname(host_uri) do + host_uri |> URI.parse() |> Map.get(:host) + end + + defp verify_scope_support(_scopes, nil, _supported_scopes), do: :ok + + defp verify_scope_support(scopes, required_scope, supported_scopes) do + required = Enum.member?(supported_scopes, required_scope) + requested = Enum.member?(String.split(scopes), required_scope) + + cond do + required && requested -> + :ok + + !required -> + {:error, "verify_scope_support", "scope '#{required_scope}' is not supported"} + + !requested -> + {:error, "verify_scope_support", "scope '#{required_scope}' was not requested"} + end + end +end diff --git a/mix.exs b/mix.exs index 7aabecf..f93de7a 100644 --- a/mix.exs +++ b/mix.exs @@ -33,31 +33,32 @@ defmodule Chiya.MixProject do defp deps do [ {:bcrypt_elixir, "~> 3.0"}, - {:phoenix, "~> 1.7.1"}, - {:phoenix_ecto, "~> 4.4"}, - {:ecto_sql, "~> 3.6"}, + {:earmark, "~> 1.4"}, {:ecto_autoslug_field, "~> 3.0"}, - {:postgrex, ">= 0.0.0"}, - {:phoenix_html, "~> 3.3"}, - {:phoenix_live_reload, "~> 1.2", only: :dev}, - {:phoenix_live_view, "~> 0.18.16"}, - {:floki, ">= 0.30.0", only: :test}, - {:phoenix_live_dashboard, "~> 0.7.2"}, - {:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev}, - {:swoosh, "~> 1.3"}, + {:ecto_sql, "~> 3.6"}, {:finch, "~> 0.16"}, - {:telemetry_metrics, "~> 0.6"}, - {:telemetry_poller, "~> 1.0"}, + {:floki, ">= 0.30.0", only: :test}, {:gettext, "~> 0.22"}, {:jason, "~> 1.2"}, - {:plug_cowboy, "~> 2.5"}, {:oban, "~> 2.14"}, + {:phoenix, "~> 1.7.1"}, + {:phoenix_ecto, "~> 4.4"}, + {:phoenix_html, "~> 3.3"}, + {:phoenix_live_dashboard, "~> 0.7.2"}, + {:phoenix_live_reload, "~> 1.2", only: :dev}, + {:phoenix_live_view, "~> 0.18.16"}, + {:plug_cowboy, "~> 2.5"}, + {:plug_micropub, git: "https://git.inhji.de/inhji/plug_micropub"}, + {:postgrex, ">= 0.0.0"}, + {:swoosh, "~> 1.3"}, + {:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev}, + {:telemetry_metrics, "~> 0.6"}, + {:telemetry_poller, "~> 1.0"}, + {:tesla, "~> 1.7"}, + {:tz, "~> 0.26.1"}, {:waffle, "~> 1.1"}, {:waffle_ecto, "~> 0.0.12"}, - {:earmark, "~> 1.4"}, - {:yaml_front_matter, "~> 1.0.0"}, - {:tz, "~> 0.26.1"}, - {:plug_micropub, git: "https://git.inhji.de/inhji/plug_micropub"} + {:yaml_front_matter, "~> 1.0.0"} ] end diff --git a/mix.lock b/mix.lock index 802cd05..0fe40ec 100644 --- a/mix.lock +++ b/mix.lock @@ -54,6 +54,7 @@ "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, + "tesla": {:hex, :tesla, "1.7.0", "a62dda2f80d4f8a925eb7b8c5b78c461e0eb996672719fe1a63b26321a5f8b4e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2e64f01ebfdb026209b47bc651a0e65203fcff4ae79c11efb73c4852b00dc313"}, "tz": {:hex, :tz, "0.26.1", "773555ecb9c01c87fcf969b4c2d2140e63fe6b3d7d9520fa2134ac1072b540a8", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.5", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "da38cea41e9cfd0deaa7f634e167a30399dcc8b84fd3da32e1d972466053f57c"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "waffle": {:hex, :waffle, "1.1.7", "518f9bdda7b9b3d0958ad6ab16066631ce028f5f12217382822a428895fc4be3", [:mix], [{:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:ex_aws_s3, "~> 2.1", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "e97e7b10b7f380687b5dc5e65b391538a802eff636605ad183e0bed29b45b0ef"},