[#114] Added email confirmation resend action. Added tests
authorIvan Tashkinov <ivantashkinov@gmail.com>
Tue, 18 Dec 2018 14:13:52 +0000 (17:13 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Tue, 18 Dec 2018 14:22:46 +0000 (17:22 +0300)
for registration, authentication, email confirmation, confirmation resending.
Made admin methods create confirmed users.

lib/mix/tasks/pleroma/user.ex
lib/pleroma/user.ex
lib/pleroma/web/oauth/oauth_controller.ex
lib/pleroma/web/router.ex
lib/pleroma/web/twitter_api/twitter_api_controller.ex
test/user_test.exs
test/web/oauth/oauth_controller_test.exs
test/web/twitter_api/twitter_api_controller_test.exs
test/web/twitter_api/twitter_api_test.exs

index 3d30e3a81cfb10a3f3280cbedcf207ce80af420f..51086a4432d3733e378ada6adfa31c0273b1f078 100644 (file)
@@ -103,8 +103,8 @@ defmodule Mix.Tasks.Pleroma.User do
         bio: bio
       }
 
-      user = User.register_changeset(%User{}, params)
-      Repo.insert!(user)
+      changeset = User.register_changeset(%User{}, params, confirmed: true)
+      {:ok, _user} = User.register(changeset)
 
       Mix.shell().info("User #{nickname} created")
 
index 234617574a8d2736e5ee9cd3e665b70ece6988be..0cd7bc46303a7f602a0df762bf031f810d68fc15 100644 (file)
@@ -74,13 +74,15 @@ defmodule Pleroma.User do
 
   def user_info(%User{} = user) do
     oneself = if user.local, do: 1, else: 0
+    user_info = user.info
 
     %{
       following_count: length(user.following) - oneself,
-      note_count: user.info.note_count,
-      follower_count: user.info.follower_count,
-      locked: user.info.locked,
-      default_scope: user.info.default_scope
+      note_count: user_info.note_count,
+      follower_count: user_info.follower_count,
+      locked: user_info.locked,
+      confirmation_pending: user_info.confirmation_pending,
+      default_scope: user_info.default_scope
     }
   end
 
@@ -209,17 +211,21 @@ defmodule Pleroma.User do
   @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
   def register(%Ecto.Changeset{} = changeset) do
     with {:ok, user} <- Repo.insert(changeset) do
-      if user.info.confirmation_pending do
-        {:ok, _} =
-          user
-          |> Pleroma.UserEmail.account_confirmation_email()
-          |> Pleroma.Mailer.deliver()
-      end
-
+      {:ok, _} = try_send_confirmation_email(user)
       {:ok, user}
     end
   end
 
+  def try_send_confirmation_email(%User{} = user) do
+    if user.info.confirmation_pending do
+      user
+      |> Pleroma.UserEmail.account_confirmation_email()
+      |> Pleroma.Mailer.deliver()
+    else
+      {:ok, :noop}
+    end
+  end
+
   def needs_update?(%User{local: true}), do: false
 
   def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
index 10158f07ed0fbb917924057d895a40ced4d1a2f5..9a972ee472100bb33a85b3db6390e7222eb7590d 100644 (file)
@@ -31,7 +31,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
       }) do
     with %User{} = user <- User.get_by_nickname_or_email(name),
          true <- Pbkdf2.checkpw(password, user.password_hash),
-         true <- User.auth_active?(user),
+         {:auth_active, true} <- {:auth_active, User.auth_active?(user)},
          %App{} = app <- Repo.get_by(App, client_id: client_id),
          {:ok, auth} <- Authorization.create_authorization(app, user) do
       # Special case: Local MastodonFE.
@@ -64,6 +64,15 @@ defmodule Pleroma.Web.OAuth.OAuthController do
 
           redirect(conn, external: url)
       end
+    else
+      {:auth_active, false} ->
+        conn
+        |> put_flash(:error, "Account confirmation pending")
+        |> put_status(:forbidden)
+        |> authorize(params)
+
+      error ->
+        error
     end
   end
 
@@ -102,7 +111,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     with %App{} = app <- get_app_from_request(conn, params),
          %User{} = user <- User.get_by_nickname_or_email(name),
          true <- Pbkdf2.checkpw(password, user.password_hash),
-         true <- User.auth_active?(user),
+         {:auth_active, true} <- {:auth_active, User.auth_active?(user)},
          {:ok, auth} <- Authorization.create_authorization(app, user),
          {:ok, token} <- Token.exchange_token(app, auth) do
       response = %{
@@ -115,6 +124,11 @@ defmodule Pleroma.Web.OAuth.OAuthController do
 
       json(conn, response)
     else
+      {:auth_active, false} ->
+        conn
+        |> put_status(:forbidden)
+        |> json(%{error: "Account confirmation pending"})
+
       _error ->
         put_status(conn, 400)
         |> json(%{error: "Invalid credentials"})
index 0e4589116665891f8fec9261e290632e13b47b31..ca069ab995e531a490d5213e4c54607a8b3c616f 100644 (file)
@@ -284,6 +284,8 @@ defmodule Pleroma.Web.Router do
 
     get("/account/confirm_email/:token", TwitterAPI.Controller, :confirm_email, as: :confirm_email)
 
+    post("/account/resend_confirmation_email", TwitterAPI.Controller, :resend_confirmation_email)
+
     get("/search", TwitterAPI.Controller, :search)
     get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
   end
index e8a3150e97655928e9dcb2c4bfd44ea959715bca..7286c153bdb3ff9ab7183ec96d555cf1c7c0b3fe 100644 (file)
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
   require Logger
 
   plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
-  plug(:fetch_flash when action in [:confirm_email])
+  plug(:fetch_flash when action in [:confirm_email, :resend_confirmation_email])
   action_fallback(:errors)
 
   def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
@@ -385,6 +385,17 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
     end
   end
 
+  def resend_confirmation_email(conn, params) do
+    nickname_or_email = params["email"] || params["nickname"]
+
+    with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
+         {:ok, _} <- User.try_send_confirmation_email(user) do
+      conn
+      |> put_flash(:info, "Email confirmation has been sent.")
+      |> json_response(:no_content, "")
+    end
+  end
+
   def update_avatar(%{assigns: %{user: user}} = conn, params) do
     {:ok, object} = ActivityPub.upload(params, type: :avatar)
     change = Changeset.change(user, %{avatar: object.data})
index 1e73770df11789849429e66b2e0fdd2d2474800e..b4d8174c67fdcfa560ab59e1394a503b03ec127d 100644 (file)
@@ -177,6 +177,48 @@ defmodule Pleroma.UserTest do
     end
   end
 
+  describe "user registration, with :account_activation_required" do
+    @full_user_data %{
+      bio: "A guy",
+      name: "my name",
+      nickname: "nick",
+      password: "test",
+      password_confirmation: "test",
+      email: "email@example.com"
+    }
+
+    setup do
+      setting = Pleroma.Config.get([:instance, :account_activation_required])
+
+      unless setting do
+        Pleroma.Config.put([:instance, :account_activation_required], true)
+        on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
+      end
+
+      :ok
+    end
+
+    test "it creates unconfirmed user" do
+      changeset = User.register_changeset(%User{}, @full_user_data)
+      assert changeset.valid?
+
+      {:ok, user} = Repo.insert(changeset)
+
+      assert user.info.confirmation_pending
+      assert user.info.confirmation_token
+    end
+
+    test "it creates confirmed user if :confirmed option is given" do
+      changeset = User.register_changeset(%User{}, @full_user_data, confirmed: true)
+      assert changeset.valid?
+
+      {:ok, user} = Repo.insert(changeset)
+
+      refute user.info.confirmation_pending
+      refute user.info.confirmation_token
+    end
+  end
+
   describe "get_or_fetch/1" do
     test "gets an existing user by nickname" do
       user = insert(:user)
index 3a902f128f8500e18ac384301becadb8f8cfcf29..55b471d8ab036e93b7bab88ea0980fe186c36f00 100644 (file)
@@ -50,6 +50,26 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     assert Repo.get_by(Token, token: token)
   end
 
+  test "issues a token for `password` grant_type with valid credentials" do
+    password = "testpassword"
+    user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
+
+    app = insert(:oauth_app)
+
+    conn =
+      build_conn()
+      |> post("/oauth/token", %{
+        "grant_type" => "password",
+        "username" => user.nickname,
+        "password" => password,
+        "client_id" => app.client_id,
+        "client_secret" => app.client_secret
+      })
+
+    assert %{"access_token" => token} = json_response(conn, 200)
+    assert Repo.get_by(Token, token: token)
+  end
+
   test "issues a token for request with HTTP basic auth client credentials" do
     user = insert(:user)
     app = insert(:oauth_app)
@@ -93,6 +113,36 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
     refute Map.has_key?(resp, "access_token")
   end
 
+  test "rejects token exchange for valid credentials belonging to unconfirmed user" do
+    password = "testpassword"
+    user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
+    info_change = Pleroma.User.Info.confirmation_update(user.info, :unconfirmed)
+
+    {:ok, user} =
+      user
+      |> Ecto.Changeset.change()
+      |> Ecto.Changeset.put_embed(:info, info_change)
+      |> Repo.update()
+
+    refute Pleroma.User.auth_active?(user)
+
+    app = insert(:oauth_app)
+
+    conn =
+      build_conn()
+      |> post("/oauth/token", %{
+        "grant_type" => "password",
+        "username" => user.nickname,
+        "password" => password,
+        "client_id" => app.client_id,
+        "client_secret" => app.client_secret
+      })
+
+    assert resp = json_response(conn, 403)
+    assert %{"error" => _} = resp
+    refute Map.has_key?(resp, "access_token")
+  end
+
   test "rejects an invalid authorization code" do
     app = insert(:oauth_app)
 
index c16c0cdc0f32759bcead129ccf2ca72cbed7669f..eb154608c23d51c9956e45e354303d5cb043bf8f 100644 (file)
@@ -873,6 +873,70 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
     end
   end
 
+  describe "GET /api/account/confirm_email/:token" do
+    setup do
+      user = insert(:user)
+      info_change = User.Info.confirmation_update(user.info, :unconfirmed)
+
+      {:ok, user} =
+        user
+        |> Changeset.change()
+        |> Changeset.put_embed(:info, info_change)
+        |> Repo.update()
+
+      assert user.info.confirmation_pending
+
+      [user: user]
+    end
+
+    test "it redirects to root url", %{conn: conn, user: user} do
+      conn = get(conn, "/api/account/confirm_email/#{user.info.confirmation_token}")
+
+      assert 302 == conn.status
+    end
+
+    test "it confirms the user account", %{conn: conn, user: user} do
+      get(conn, "/api/account/confirm_email/#{user.info.confirmation_token}")
+
+      user = Repo.get(User, user.id)
+
+      refute user.info.confirmation_pending
+      refute user.info.confirmation_token
+    end
+  end
+
+  describe "POST /api/account/resend_confirmation_email" do
+    setup do
+      user = insert(:user)
+      info_change = User.Info.confirmation_update(user.info, :unconfirmed)
+
+      {:ok, user} =
+        user
+        |> Changeset.change()
+        |> Changeset.put_embed(:info, info_change)
+        |> Repo.update()
+
+      assert user.info.confirmation_pending
+
+      [user: user]
+    end
+
+    test "it returns 204 No Content", %{conn: conn, user: user} do
+      conn
+      |> assign(:user, user)
+      |> post("/api/account/resend_confirmation_email?email=#{user.email}")
+      |> json_response(:no_content)
+    end
+
+    test "it sends confirmation email", %{conn: conn, user: user} do
+      conn
+      |> assign(:user, user)
+      |> post("/api/account/resend_confirmation_email?email=#{user.email}")
+
+      Swoosh.TestAssertions.assert_email_sent(Pleroma.UserEmail.account_confirmation_email(user))
+    end
+  end
+
   describe "GET /api/externalprofile/show" do
     test "it returns the user", %{conn: conn} do
       user = insert(:user)
index 3d3a637b7d892a2d24371a7537aef8fb48b6a575..b7c89b6055d03aa6536b06c3dd2c20049faefb58 100644 (file)
@@ -275,6 +275,31 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
              UserView.render("show.json", %{user: fetched_user})
   end
 
+  @moduletag skip: "needs 'account_activation_required: true' in config"
+  test "it sends confirmation email if :account_activation_required is specified in instance config" do
+    setting = Pleroma.Config.get([:instance, :account_activation_required])
+
+    unless setting do
+      Pleroma.Config.put([:instance, :account_activation_required], true)
+      on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
+    end
+
+    data = %{
+      "nickname" => "lain",
+      "email" => "lain@wired.jp",
+      "fullname" => "lain iwakura",
+      "bio" => "",
+      "password" => "bear",
+      "confirm" => "bear"
+    }
+
+    {:ok, user} = TwitterAPI.register_user(data)
+
+    assert user.info.confirmation_pending
+
+    Swoosh.TestAssertions.assert_email_sent(Pleroma.UserEmail.account_confirmation_email(user))
+  end
+
   test "it registers a new user and parses mentions in the bio" do
     data1 = %{
       "nickname" => "john",