diff --git a/lib/asciinema_web.ex b/lib/asciinema_web.ex index 66619ba1d..8622098b9 100644 --- a/lib/asciinema_web.ex +++ b/lib/asciinema_web.ex @@ -177,6 +177,13 @@ defmodule AsciinemaWeb do end end + def json do + quote do + # Routes generation with the ~p sigil + unquote(verified_routes()) + end + end + def verified_routes do quote do use Phoenix.VerifiedRoutes, diff --git a/lib/asciinema_web/controllers/webfinger_controller.ex b/lib/asciinema_web/controllers/webfinger_controller.ex new file mode 100644 index 000000000..43eb61e2b --- /dev/null +++ b/lib/asciinema_web/controllers/webfinger_controller.ex @@ -0,0 +1,20 @@ +defmodule AsciinemaWeb.WebFingerController do + use AsciinemaWeb, :new_controller + alias Asciinema.Accounts + + def show(conn, %{"resource" => resource}) do + resource = + resource + |> String.trim() + |> String.downcase() + + with "acct:" <> acct <- resource, + [username, domain] <- String.split(acct, "@"), + ^domain <- AsciinemaWeb.Endpoint.host(), + {:username, user} when not is_nil(user) <- Accounts.lookup_user(username) do + render(conn, :show, user: user, domain: domain) + else + _ -> {:error, :not_found} + end + end +end diff --git a/lib/asciinema_web/controllers/webfinger_json.ex b/lib/asciinema_web/controllers/webfinger_json.ex new file mode 100644 index 000000000..bace4de08 --- /dev/null +++ b/lib/asciinema_web/controllers/webfinger_json.ex @@ -0,0 +1,19 @@ +defmodule AsciinemaWeb.WebFingerJSON do + use AsciinemaWeb, :json + + def show(%{user: %{username: username}, domain: domain}) do + %{ + subject: "acct:#{username}@#{domain}", + aliases: [ + url(~p"/~#{username}") + ], + links: [ + %{ + rel: "http://webfinger.net/rel/profile-page", + type: "text/html", + href: url(~p"/~#{username}") + } + ] + } + end +end diff --git a/lib/asciinema_web/router.ex b/lib/asciinema_web/router.ex index fcf2b93f9..31183e075 100644 --- a/lib/asciinema_web/router.ex +++ b/lib/asciinema_web/router.ex @@ -42,6 +42,10 @@ defmodule AsciinemaWeb.Router do plug :put_secure_browser_headers end + pipeline :webfinger do + plug :put_format, :json + end + scope "/", AsciinemaWeb do pipe_through :asciicast @@ -54,6 +58,11 @@ defmodule AsciinemaWeb.Router do get "/oembed", OembedController, :show end + scope "/", AsciinemaWeb do + pipe_through :webfinger + get "/.well-known/webfinger", WebFingerController, :show + end + scope "/", AsciinemaWeb do # Use the default browser stack pipe_through :browser diff --git a/test/controllers/webfinger_controller_test.exs b/test/controllers/webfinger_controller_test.exs new file mode 100644 index 000000000..a9afb2ce8 --- /dev/null +++ b/test/controllers/webfinger_controller_test.exs @@ -0,0 +1,48 @@ +defmodule Asciinema.WebFingerControllerTest do + use AsciinemaWeb.ConnCase + import Asciinema.Factory + + setup [:create_user] + + describe "show" do + test "returns basic info", %{conn: conn} do + conn = get(conn, ~p"/.well-known/webfinger?resource=acct:Pinky@localhost") + + assert json_response(conn, 200) == %{ + "subject" => "acct:Pinky@localhost", + "aliases" => [ + url(~p"/~Pinky") + ], + "links" => [ + %{ + "rel" => "http://webfinger.net/rel/profile-page", + "type" => "text/html", + "href" => url(~p"/~Pinky") + } + ] + } + end + + test "does case-insensitive acct lookup", %{conn: conn} do + conn = get(conn, ~p"/.well-known/webfinger?resource=acct:pinky@lOcaLhOst") + + assert %{"subject" => "acct:Pinky@localhost"} = json_response(conn, 200) + end + + test "returns 404 when username not found", %{conn: conn} do + conn = get(conn, ~p"/.well-known/webfinger?resource=acct:nope@localhost") + + assert json_response(conn, 404) + end + + test "returns 404 when domain doesn't match", %{conn: conn} do + conn = get(conn, ~p"/.well-known/webfinger?resource=acct:pinky@nope.nope") + + assert json_response(conn, 404) + end + end + + defp create_user(_) do + %{user: insert(:user, username: "Pinky")} + end +end