Password Resets: Don't accept tokens above a certain age.
authorlain <lain@soykaf.club>
Thu, 19 Nov 2020 11:27:06 +0000 (12:27 +0100)
committerlain <lain@soykaf.club>
Thu, 19 Nov 2020 11:27:06 +0000 (12:27 +0100)
By default, one day

config/config.exs
lib/pleroma/password_reset_token.ex
lib/pleroma/web/twitter_api/controllers/password_controller.ex
test/pleroma/web/twitter_api/password_controller_test.exs

index 1ac140ed0e067c5ddbccd3a9f70ca4a71915e560..be52576631b33e38c2cb4a75cc8b24d614c11687 100644 (file)
@@ -263,7 +263,8 @@ config :pleroma, :instance,
       length: 16
     ]
   ],
-  show_reactions: true
+  show_reactions: true,
+  password_reset_token_validity: 60 * 60 * 24
 
 config :pleroma, :welcome,
   direct_message: [
index 787bd47812221ec6dd41c051f889db91f8cf2f60..fea5b1c22cc3227a0e90829f9ae25539e64ea612 100644 (file)
@@ -40,6 +40,7 @@ defmodule Pleroma.PasswordResetToken do
   @spec reset_password(binary(), map()) :: {:ok, User.t()} | {:error, binary()}
   def reset_password(token, data) do
     with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
+         false <- expired?(token),
          %User{} = user <- User.get_cached_by_id(token.user_id),
          {:ok, _user} <- User.reset_password(user, data),
          {:ok, token} <- Repo.update(used_changeset(token)) do
@@ -48,4 +49,14 @@ defmodule Pleroma.PasswordResetToken do
       _e -> {:error, token}
     end
   end
+
+  def expired?(%__MODULE__{inserted_at: inserted_at}) do
+    validity = Pleroma.Config.get([:instance, :password_reset_token_validity], 0)
+
+    now = NaiveDateTime.utc_now()
+
+    difference = NaiveDateTime.diff(now, inserted_at)
+
+    difference > validity
+  end
 end
index 800ab8954dd6e71a84aa77c322902ee8dcab37f1..b1a9d810e0921ffdd362b8919897da5b7ef5fbc3 100644 (file)
@@ -17,6 +17,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do
 
   def reset(conn, %{"token" => token}) do
     with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
+         false <- PasswordResetToken.expired?(token),
          %User{} = user <- User.get_cached_by_id(token.user_id) do
       render(conn, "reset.html", %{
         token: token,
index a5e9e2178d84d95bdde9cefb534c3d51533c7fd2..6d08075cc5187e4b36eae23ab1d4f3237f11a2ac 100644 (file)
@@ -31,9 +31,48 @@ defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do
 
       assert response =~ "<h2>Password Reset for #{user.nickname}</h2>"
     end
+
+    test "it returns an error when the token has expired", %{conn: conn} do
+      clear_config([:instance, :password_reset_token_validity], 0)
+
+      user = insert(:user)
+      {:ok, token} = PasswordResetToken.create_token(user)
+
+      :timer.sleep(2000)
+
+      response =
+        conn
+        |> get("/api/pleroma/password_reset/#{token.token}")
+        |> html_response(:ok)
+
+      assert response =~ "<h2>Invalid Token</h2>"
+    end
   end
 
   describe "POST /api/pleroma/password_reset" do
+    test "it fails for an expired token", %{conn: conn} do
+      clear_config([:instance, :password_reset_token_validity], 0)
+
+      user = insert(:user)
+      {:ok, token} = PasswordResetToken.create_token(user)
+      :timer.sleep(2000)
+      {:ok, _access_token} = Token.create(insert(:oauth_app), user, %{})
+
+      params = %{
+        "password" => "test",
+        password_confirmation: "test",
+        token: token.token
+      }
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> post("/api/pleroma/password_reset", %{data: params})
+        |> html_response(:ok)
+
+      refute response =~ "<h2>Password changed!</h2>"
+    end
+
     test "it returns HTTP 200", %{conn: conn} do
       user = insert(:user)
       {:ok, token} = PasswordResetToken.create_token(user)