From f42ffbe9a855494c182c97f5eb641e800e562aa4 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 12 Jun 2018 14:52:54 +0300 Subject: [PATCH] Initial invites support + tests. --- lib/pleroma/UserInviteToken.ex | 40 ++++++++++++ lib/pleroma/web/router.ex | 4 +- lib/pleroma/web/twitter_api/twitter_api.ex | 36 +++++++--- ...180612110515_create_user_invite_tokens.exs | 12 ++++ test/web/twitter_api/twitter_api_test.exs | 65 ++++++++++++++++++- 5 files changed, 143 insertions(+), 14 deletions(-) create mode 100644 lib/pleroma/UserInviteToken.ex create mode 100644 priv/repo/migrations/20180612110515_create_user_invite_tokens.exs diff --git a/lib/pleroma/UserInviteToken.ex b/lib/pleroma/UserInviteToken.ex new file mode 100644 index 000000000..48ee1019a --- /dev/null +++ b/lib/pleroma/UserInviteToken.ex @@ -0,0 +1,40 @@ +defmodule Pleroma.UserInviteToken do + use Ecto.Schema + + import Ecto.Changeset + + alias Pleroma.{User, UserInviteToken, Repo} + + schema "user_invite_tokens" do + field(:token, :string) + field(:used, :boolean, default: false) + + timestamps() + end + + def create_token do + token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() + + token = %UserInviteToken{ + used: false, + token: token + } + + Repo.insert(token) + end + + def used_changeset(struct) do + struct + |> cast(%{}, []) + |> put_change(:used, true) + end + + def mark_as_used(token) do + with %{used: false} = token <- Repo.get_by(UserInviteToken, %{token: token}), + {:ok, token} <- Repo.update(used_changeset(token)) do + {:ok, token} + else + _e -> {:error, token} + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index ee6a373d3..127bf4d9e 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -194,9 +194,7 @@ def user_fetcher(username) do get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status) get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation) - if @registrations_open do - post("/account/register", TwitterAPI.Controller, :register) - end + post("/account/register", TwitterAPI.Controller, :register) get("/search", TwitterAPI.Controller, :search) get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index ccc6fe8e7..8608ee9ac 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -1,11 +1,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do - alias Pleroma.{User, Activity, Repo, Object} + alias Pleroma.{UserInviteToken, User, Activity, Repo, Object} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.TwitterAPI.UserView alias Pleroma.Web.{OStatus, CommonAPI} import Ecto.Query + @instance Application.get_env(:pleroma, :instance) @httpoison Application.get_env(:pleroma, :httpoison) + @registrations_open Keyword.get(@instance, :registrations_open) def create_status(%User{} = user, %{"status" => _} = data) do CommonAPI.post(user, data) @@ -124,6 +126,8 @@ def upload(%Plug.Upload{} = file, format \\ "xml") do end def register_user(params) do + tokenString = params["token"] + params = %{ nickname: params["nickname"], name: params["fullname"], @@ -133,17 +137,29 @@ def register_user(params) do password_confirmation: params["confirm"] } - changeset = User.register_changeset(%User{}, params) + # no need to query DB if registration is open + unless @registrations_open || is_nil(tokenString) do + token = Repo.get_by(UserInviteToken, %{token: tokenString}) + end - with {:ok, user} <- Repo.insert(changeset) do - {:ok, user} - else - {:error, changeset} -> - errors = - Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) - |> Jason.encode!() + cond do + @registrations_open || !is_nil(token) && !token.used -> + changeset = User.register_changeset(%User{}, params) - {:error, %{error: errors}} + with {:ok, user} <- Repo.insert(changeset) do + !@registrations_open && UserInviteToken.mark_as_used(token.token) + {:ok, user} + else + {:error, changeset} -> + errors = + Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) + |> Jason.encode!() + + {:error, %{error: errors}} + end + + !@registrations_open && is_nil(token) -> {:error, "Invalid token"} + !@registrations_open && token.used -> {:error, "Expired token"} end end diff --git a/priv/repo/migrations/20180612110515_create_user_invite_tokens.exs b/priv/repo/migrations/20180612110515_create_user_invite_tokens.exs new file mode 100644 index 000000000..d0a1cf784 --- /dev/null +++ b/priv/repo/migrations/20180612110515_create_user_invite_tokens.exs @@ -0,0 +1,12 @@ +defmodule Pleroma.Repo.Migrations.CreateUserInviteTokens do + use Ecto.Migration + + def change do + create table(:user_invite_tokens) do + add :token, :string + add :used, :boolean, default: false + + timestamps() + end + end +end diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index edacb312d..ed9158bf5 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -2,7 +2,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do use Pleroma.DataCase alias Pleroma.Builders.UserBuilder alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView} - alias Pleroma.{Activity, User, Object, Repo} + alias Pleroma.{Activity, User, Object, Repo, UserInviteToken} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.TwitterAPI.ActivityView @@ -246,6 +246,69 @@ test "it registers a new user and returns the user." do UserView.render("show.json", %{user: fetched_user}) end + @moduletag skip: "needs 'registrations_open: false' in config" + test "it registers a new user via invite token and returns the user." do + {:ok, token} = UserInviteToken.create_token() + + data = %{ + "nickname" => "vinny", + "email" => "pasta@pizza.vs", + "fullname" => "Vinny Vinesauce", + "bio" => "streamer", + "password" => "hiptofbees", + "confirm" => "hiptofbees", + "token" => token.token + } + + {:ok, user} = TwitterAPI.register_user(data) + + fetched_user = Repo.get_by(User, nickname: "vinny") + token = Repo.get_by(UserInviteToken, token: token.token) + + assert token.used == true + assert UserView.render("show.json", %{user: user}) == + UserView.render("show.json", %{user: fetched_user}) + end + + @moduletag skip: "needs 'registrations_open: false' in config" + test "it returns an error if invalid token submitted" do + data = %{ + "nickname" => "GrimReaper", + "email" => "death@reapers.afterlife", + "fullname" => "Reaper Grim", + "bio" => "Your time has come", + "password" => "scythe", + "confirm" => "scythe", + "token" => "DudeLetMeInImAFairy" + } + + {:error, msg} = TwitterAPI.register_user(data) + + assert msg == "Invalid token" + refute Repo.get_by(User, nickname: "GrimReaper") + end + + @moduletag skip: "needs 'registrations_open: false' in config" + test "it returns an error if expired token submitted" do + {:ok, token} = UserInviteToken.create_token() + UserInviteToken.mark_as_used(token.token) + + data = %{ + "nickname" => "GrimReaper", + "email" => "death@reapers.afterlife", + "fullname" => "Reaper Grim", + "bio" => "Your time has come", + "password" => "scythe", + "confirm" => "scythe", + "token" => token.token + } + + {:error, msg} = TwitterAPI.register_user(data) + + assert msg == "Expired token" + refute Repo.get_by(User, nickname: "GrimReaper") + end + test "it returns the error on registration problems" do data = %{ "nickname" => "lain",