Initial invites support + tests.
authorHenry Jameson <me@hjkos.com>
Tue, 12 Jun 2018 11:52:54 +0000 (14:52 +0300)
committerHenry Jameson <me@hjkos.com>
Tue, 12 Jun 2018 11:55:16 +0000 (14:55 +0300)
lib/pleroma/UserInviteToken.ex [new file with mode: 0644]
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/twitter_api.ex
priv/repo/migrations/20180612110515_create_user_invite_tokens.exs [new file with mode: 0644]
test/web/twitter_api/twitter_api_test.exs

diff --git a/lib/pleroma/UserInviteToken.ex b/lib/pleroma/UserInviteToken.ex
new file mode 100644 (file)
index 0000000..48ee101
--- /dev/null
@@ -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
index ee6a373d3ed27b1222b7d20817d74c0b9266ae04..127bf4d9eed371e30d2920f6ee1a7541546d98da 100644 (file)
@@ -194,9 +194,7 @@ defmodule Pleroma.Web.Router 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)
index ccc6fe8e7be07fa7ccdb902cd20979e8c1ce6e10..8608ee9ac08ce106dc1c1384a35d6c79a5489ee9 100644 (file)
@@ -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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
   end
 
   def register_user(params) do
+    tokenString = params["token"]
+
     params = %{
       nickname: params["nickname"],
       name: params["fullname"],
@@ -133,17 +137,29 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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
+
+    cond do
+      @registrations_open || !is_nil(token) && !token.used ->
+        changeset = User.register_changeset(%User{}, params)
 
-    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!()
+        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
 
-        {:error, %{error: errors}}
+      !@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 (file)
index 0000000..d0a1cf7
--- /dev/null
@@ -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
index edacb312d142a3a1ace59bfee9356ee9dd7e46bd..ed9158bf5d86fa40ca353c8cd586bbf97c664c52 100644 (file)
@@ -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 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest 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",