@@ -0,0 +1,91 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.MastodonAPI.AuthController do | |||
use Pleroma.Web, :controller | |||
alias Pleroma.User | |||
alias Pleroma.Web.OAuth.App | |||
alias Pleroma.Web.OAuth.Authorization | |||
alias Pleroma.Web.OAuth.Token | |||
alias Pleroma.Web.TwitterAPI.TwitterAPI | |||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) | |||
@local_mastodon_name "Mastodon-Local" | |||
plug(Pleroma.Plugs.RateLimiter, :password_reset when action == :password_reset) | |||
@doc "GET /web/login" | |||
def login(%{assigns: %{user: %User{}}} = conn, _params) do | |||
redirect(conn, to: local_mastodon_root_path(conn)) | |||
end | |||
@doc "Local Mastodon FE login init action" | |||
def login(conn, %{"code" => auth_token}) do | |||
with {:ok, app} <- get_or_make_app(), | |||
{:ok, auth} <- Authorization.get_by_token(app, auth_token), | |||
{:ok, token} <- Token.exchange_token(app, auth) do | |||
conn | |||
|> put_session(:oauth_token, token.token) | |||
|> redirect(to: local_mastodon_root_path(conn)) | |||
end | |||
end | |||
@doc "Local Mastodon FE callback action" | |||
def login(conn, _) do | |||
with {:ok, app} <- get_or_make_app() do | |||
path = | |||
o_auth_path(conn, :authorize, | |||
response_type: "code", | |||
client_id: app.client_id, | |||
redirect_uri: ".", | |||
scope: Enum.join(app.scopes, " ") | |||
) | |||
redirect(conn, to: path) | |||
end | |||
end | |||
@doc "DELETE /auth/sign_out" | |||
def logout(conn, _) do | |||
conn | |||
|> clear_session | |||
|> redirect(to: "/") | |||
end | |||
@doc "POST /auth/password" | |||
def password_reset(conn, params) do | |||
nickname_or_email = params["email"] || params["nickname"] | |||
with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do | |||
conn | |||
|> put_status(:no_content) | |||
|> json("") | |||
else | |||
{:error, "unknown user"} -> | |||
send_resp(conn, :not_found, "") | |||
{:error, _} -> | |||
send_resp(conn, :bad_request, "") | |||
end | |||
end | |||
defp local_mastodon_root_path(conn) do | |||
case get_session(conn, :return_to) do | |||
nil -> | |||
mastodon_api_path(conn, :index, ["getting-started"]) | |||
return_to -> | |||
delete_session(conn, :return_to) | |||
return_to | |||
end | |||
end | |||
@spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} | |||
defp get_or_make_app do | |||
%{client_name: @local_mastodon_name, redirect_uris: "."} | |||
|> App.get_or_make(["read", "write", "follow", "push"]) | |||
end | |||
end |
@@ -10,7 +10,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
alias Pleroma.Bookmark | |||
alias Pleroma.Config | |||
alias Pleroma.Pagination | |||
alias Pleroma.Plugs.RateLimiter | |||
alias Pleroma.Stats | |||
alias Pleroma.User | |||
alias Pleroma.Web | |||
@@ -19,18 +18,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
alias Pleroma.Web.MastodonAPI.AccountView | |||
alias Pleroma.Web.MastodonAPI.MastodonView | |||
alias Pleroma.Web.MastodonAPI.StatusView | |||
alias Pleroma.Web.OAuth.App | |||
alias Pleroma.Web.OAuth.Authorization | |||
alias Pleroma.Web.OAuth.Token | |||
alias Pleroma.Web.TwitterAPI.TwitterAPI | |||
require Logger | |||
plug(RateLimiter, :password_reset when action == :password_reset) | |||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) | |||
@local_mastodon_name "Mastodon-Local" | |||
@mastodon_api_level "2.7.2" | |||
def masto_instance(conn, _params) do | |||
@@ -264,61 +256,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
end | |||
end | |||
def login(%{assigns: %{user: %User{}}} = conn, _params) do | |||
redirect(conn, to: local_mastodon_root_path(conn)) | |||
end | |||
@doc "Local Mastodon FE login init action" | |||
def login(conn, %{"code" => auth_token}) do | |||
with {:ok, app} <- get_or_make_app(), | |||
{:ok, auth} <- Authorization.get_by_token(app, auth_token), | |||
{:ok, token} <- Token.exchange_token(app, auth) do | |||
conn | |||
|> put_session(:oauth_token, token.token) | |||
|> redirect(to: local_mastodon_root_path(conn)) | |||
end | |||
end | |||
@doc "Local Mastodon FE callback action" | |||
def login(conn, _) do | |||
with {:ok, app} <- get_or_make_app() do | |||
path = | |||
o_auth_path(conn, :authorize, | |||
response_type: "code", | |||
client_id: app.client_id, | |||
redirect_uri: ".", | |||
scope: Enum.join(app.scopes, " ") | |||
) | |||
redirect(conn, to: path) | |||
end | |||
end | |||
defp local_mastodon_root_path(conn) do | |||
case get_session(conn, :return_to) do | |||
nil -> | |||
mastodon_api_path(conn, :index, ["getting-started"]) | |||
return_to -> | |||
delete_session(conn, :return_to) | |||
return_to | |||
end | |||
end | |||
@spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} | |||
defp get_or_make_app do | |||
App.get_or_make( | |||
%{client_name: @local_mastodon_name, redirect_uris: "."}, | |||
["read", "write", "follow", "push"] | |||
) | |||
end | |||
def logout(conn, _) do | |||
conn | |||
|> clear_session | |||
|> redirect(to: "/") | |||
end | |||
# Stubs for unimplemented mastodon api | |||
# | |||
def empty_array(conn, _) do | |||
@@ -331,22 +268,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
json(conn, %{}) | |||
end | |||
def password_reset(conn, params) do | |||
nickname_or_email = params["email"] || params["nickname"] | |||
with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do | |||
conn | |||
|> put_status(:no_content) | |||
|> json("") | |||
else | |||
{:error, "unknown user"} -> | |||
send_resp(conn, :not_found, "") | |||
{:error, _} -> | |||
send_resp(conn, :bad_request, "") | |||
end | |||
end | |||
defp present?(nil), do: false | |||
defp present?(false), do: false | |||
defp present?(_), do: true | |||
@@ -661,10 +661,10 @@ defmodule Pleroma.Web.Router do | |||
scope "/", Pleroma.Web.MastodonAPI do | |||
pipe_through(:mastodon_html) | |||
get("/web/login", MastodonAPIController, :login) | |||
delete("/auth/sign_out", MastodonAPIController, :logout) | |||
get("/web/login", AuthController, :login) | |||
delete("/auth/sign_out", AuthController, :logout) | |||
post("/auth/password", MastodonAPIController, :password_reset) | |||
post("/auth/password", AuthController, :password_reset) | |||
scope [] do | |||
pipe_through(:oauth_read) | |||
@@ -0,0 +1,121 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do | |||
use Pleroma.Web.ConnCase | |||
alias Pleroma.Config | |||
alias Pleroma.Repo | |||
alias Pleroma.Tests.ObanHelpers | |||
import Pleroma.Factory | |||
import Swoosh.TestAssertions | |||
describe "GET /web/login" do | |||
setup %{conn: conn} do | |||
session_opts = [ | |||
store: :cookie, | |||
key: "_test", | |||
signing_salt: "cooldude" | |||
] | |||
conn = | |||
conn | |||
|> Plug.Session.call(Plug.Session.init(session_opts)) | |||
|> fetch_session() | |||
test_path = "/web/statuses/test" | |||
%{conn: conn, path: test_path} | |||
end | |||
test "redirects to the saved path after log in", %{conn: conn, path: path} do | |||
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") | |||
auth = insert(:oauth_authorization, app: app) | |||
conn = | |||
conn | |||
|> put_session(:return_to, path) | |||
|> get("/web/login", %{code: auth.token}) | |||
assert conn.status == 302 | |||
assert redirected_to(conn) == path | |||
end | |||
test "redirects to the getting-started page when referer is not present", %{conn: conn} do | |||
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") | |||
auth = insert(:oauth_authorization, app: app) | |||
conn = get(conn, "/web/login", %{code: auth.token}) | |||
assert conn.status == 302 | |||
assert redirected_to(conn) == "/web/getting-started" | |||
end | |||
end | |||
describe "POST /auth/password, with valid parameters" do | |||
setup %{conn: conn} do | |||
user = insert(:user) | |||
conn = post(conn, "/auth/password?email=#{user.email}") | |||
%{conn: conn, user: user} | |||
end | |||
test "it returns 204", %{conn: conn} do | |||
assert json_response(conn, :no_content) | |||
end | |||
test "it creates a PasswordResetToken record for user", %{user: user} do | |||
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) | |||
assert token_record | |||
end | |||
test "it sends an email to user", %{user: user} do | |||
ObanHelpers.perform_all() | |||
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) | |||
email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) | |||
notify_email = Config.get([:instance, :notify_email]) | |||
instance_name = Config.get([:instance, :name]) | |||
assert_email_sent( | |||
from: {instance_name, notify_email}, | |||
to: {user.name, user.email}, | |||
html_body: email.html_body | |||
) | |||
end | |||
end | |||
describe "POST /auth/password, with invalid parameters" do | |||
setup do | |||
user = insert(:user) | |||
{:ok, user: user} | |||
end | |||
test "it returns 404 when user is not found", %{conn: conn, user: user} do | |||
conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") | |||
assert conn.status == 404 | |||
assert conn.resp_body == "" | |||
end | |||
test "it returns 400 when user is not local", %{conn: conn, user: user} do | |||
{:ok, user} = Repo.update(Ecto.Changeset.change(user, local: false)) | |||
conn = post(conn, "/auth/password?email=#{user.email}") | |||
assert conn.status == 400 | |||
assert conn.resp_body == "" | |||
end | |||
end | |||
describe "DELETE /auth/sign_out" do | |||
test "redirect to root page", %{conn: conn} do | |||
user = insert(:user) | |||
conn = | |||
conn | |||
|> assign(:user, user) | |||
|> delete("/auth/sign_out") | |||
assert conn.status == 302 | |||
assert redirected_to(conn) == "/" | |||
end | |||
end | |||
end |
@@ -9,12 +9,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
alias Pleroma.Config | |||
alias Pleroma.Notification | |||
alias Pleroma.Repo | |||
alias Pleroma.Tests.ObanHelpers | |||
alias Pleroma.User | |||
alias Pleroma.Web.CommonAPI | |||
import Pleroma.Factory | |||
import Swoosh.TestAssertions | |||
import Tesla.Mock | |||
setup do | |||
@@ -303,95 +301,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
assert return_to == path | |||
end | |||
test "redirects to the saved path after log in", %{conn: conn, path: path} do | |||
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") | |||
auth = insert(:oauth_authorization, app: app) | |||
conn = | |||
conn | |||
|> put_session(:return_to, path) | |||
|> get("/web/login", %{code: auth.token}) | |||
assert conn.status == 302 | |||
assert redirected_to(conn) == path | |||
end | |||
test "redirects to the getting-started page when referer is not present", %{conn: conn} do | |||
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") | |||
auth = insert(:oauth_authorization, app: app) | |||
conn = get(conn, "/web/login", %{code: auth.token}) | |||
assert conn.status == 302 | |||
assert redirected_to(conn) == "/web/getting-started" | |||
end | |||
end | |||
describe "POST /auth/password, with valid parameters" do | |||
setup %{conn: conn} do | |||
user = insert(:user) | |||
conn = post(conn, "/auth/password?email=#{user.email}") | |||
%{conn: conn, user: user} | |||
end | |||
test "it returns 204", %{conn: conn} do | |||
assert json_response(conn, :no_content) | |||
end | |||
test "it creates a PasswordResetToken record for user", %{user: user} do | |||
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) | |||
assert token_record | |||
end | |||
test "it sends an email to user", %{user: user} do | |||
ObanHelpers.perform_all() | |||
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) | |||
email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) | |||
notify_email = Config.get([:instance, :notify_email]) | |||
instance_name = Config.get([:instance, :name]) | |||
assert_email_sent( | |||
from: {instance_name, notify_email}, | |||
to: {user.name, user.email}, | |||
html_body: email.html_body | |||
) | |||
end | |||
end | |||
describe "POST /auth/password, with invalid parameters" do | |||
setup do | |||
user = insert(:user) | |||
{:ok, user: user} | |||
end | |||
test "it returns 404 when user is not found", %{conn: conn, user: user} do | |||
conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") | |||
assert conn.status == 404 | |||
assert conn.resp_body == "" | |||
end | |||
test "it returns 400 when user is not local", %{conn: conn, user: user} do | |||
{:ok, user} = Repo.update(Changeset.change(user, local: false)) | |||
conn = post(conn, "/auth/password?email=#{user.email}") | |||
assert conn.status == 400 | |||
assert conn.resp_body == "" | |||
end | |||
end | |||
describe "DELETE /auth/sign_out" do | |||
test "redirect to root page", %{conn: conn} do | |||
user = insert(:user) | |||
conn = | |||
conn | |||
|> assign(:user, user) | |||
|> delete("/auth/sign_out") | |||
assert conn.status == 302 | |||
assert redirected_to(conn) == "/" | |||
end | |||
end | |||
describe "empty_array, stubs for mastodon api" do | |||