diff --git a/CHANGELOG.md b/CHANGELOG.md index decf9ef47..ecefba381 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Allow users to remove their emails if instance does not need email to register ### Added +- `activeMonth` and `activeHalfyear` fields in NodeInfo usage.users object ### Fixed - Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 3b4e49176..8e40dfc0d 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -2474,8 +2474,8 @@ def update_last_active_at(%__MODULE__{local: true} = user) do |> update_and_set_cache() end - def active_user_count(weeks \\ 4) do - active_after = Timex.shift(NaiveDateTime.utc_now(), weeks: -weeks) + def active_user_count(days \\ 30) do + active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days) __MODULE__ |> where([u], u.last_active_at >= ^active_after) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex index 6a0112d2a..3781781c8 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex @@ -35,7 +35,9 @@ def get_nodeinfo("2.0") do openRegistrations: Config.get([:instance, :registrations_open]), usage: %{ users: %{ - total: Map.get(stats, :user_count, 0) + total: Map.get(stats, :user_count, 0), + activeMonth: Pleroma.User.active_user_count(30), + activeHalfyear: Pleroma.User.active_user_count(180) }, localPosts: Map.get(stats, :status_count, 0) }, diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index abb332ec2..dae113617 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -736,6 +736,12 @@ defmodule Pleroma.Web.Router do get("/:version", Nodeinfo.NodeinfoController, :nodeinfo) end + scope "/", Pleroma.Web do + pipe_through(:pleroma_html) + + post("/auth/password", TwitterAPI.PasswordController, :request) + end + scope "/proxy/", Pleroma.Web do get("/preview/:sig/:url", MediaProxy.MediaProxyController, :preview) get("/preview/:sig/:url/:filename", MediaProxy.MediaProxyController, :preview) diff --git a/lib/pleroma/web/twitter_api/controllers/password_controller.ex b/lib/pleroma/web/twitter_api/controllers/password_controller.ex index bc04a4d49..133a588b0 100644 --- a/lib/pleroma/web/twitter_api/controllers/password_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/password_controller.ex @@ -11,9 +11,23 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do require Logger + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + alias Pleroma.PasswordResetToken alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.TwitterAPI.TwitterAPI + + plug(Pleroma.Web.Plugs.RateLimiter, [name: :request] when action == :request) + + @doc "POST /auth/password" + def request(conn, params) do + nickname_or_email = params["email"] || params["nickname"] + + TwitterAPI.password_reset(nickname_or_email) + + json_response(conn, :no_content, "") + end def reset(conn, %{"token" => token}) do with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), diff --git a/mix.exs b/mix.exs index 4ec76a50f..9385f7cf4 100644 --- a/mix.exs +++ b/mix.exs @@ -208,7 +208,7 @@ defp deps do {:mock, "~> 0.3.5", only: :test}, # temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed {:excoveralls, "0.12.3", only: :test}, - {:hackney, "~> 1.17.0", override: true}, + {:hackney, "~> 1.18.0", override: true}, {:mox, "~> 1.0", only: :test}, {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test} ] ++ oauth_deps() diff --git a/priv/repo/migrations/20211125110126_force_pinned_objects_to_exist.exs b/priv/repo/migrations/20211125110126_force_pinned_objects_to_exist.exs new file mode 100644 index 000000000..1fe9271f0 --- /dev/null +++ b/priv/repo/migrations/20211125110126_force_pinned_objects_to_exist.exs @@ -0,0 +1,11 @@ +defmodule Pleroma.Repo.Migrations.ForcePinnedObjectsToExist do + use Ecto.Migration + + def change do + execute("UPDATE users SET pinned_objects = '{}' WHERE pinned_objects IS NULL") + + alter table("users") do + modify(:pinned_objects, :map, null: false, default: %{}) + end + end +end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 5fef81245..12d5d5db6 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -2410,13 +2410,16 @@ test "update_last_active_at/1" do test "active_user_count/1" do insert(:user) insert(:user, %{local: false}) - insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), weeks: -5)}) - insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), weeks: -3)}) insert(:user, %{last_active_at: NaiveDateTime.utc_now()}) + insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), days: -15)}) + insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), weeks: -6)}) + insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), months: -7)}) + insert(:user, %{last_active_at: Timex.shift(NaiveDateTime.utc_now(), years: -2)}) assert User.active_user_count() == 2 - assert User.active_user_count(6) == 3 - assert User.active_user_count(1) == 1 + assert User.active_user_count(180) == 3 + assert User.active_user_count(365) == 4 + assert User.active_user_count(1000) == 5 end describe "pins" do diff --git a/test/pleroma/web/plugs/frontend_static_plug_test.exs b/test/pleroma/web/plugs/frontend_static_plug_test.exs index a9342e6f0..82e955c25 100644 --- a/test/pleroma/web/plugs/frontend_static_plug_test.exs +++ b/test/pleroma/web/plugs/frontend_static_plug_test.exs @@ -94,6 +94,7 @@ test "api routes are detected correctly" do "internal", ".well-known", "nodeinfo", + "auth", "proxy", "test", "user_exists", diff --git a/test/pleroma/web/twitter_api/password_controller_test.exs b/test/pleroma/web/twitter_api/password_controller_test.exs index cf99e2434..45ab10a8a 100644 --- a/test/pleroma/web/twitter_api/password_controller_test.exs +++ b/test/pleroma/web/twitter_api/password_controller_test.exs @@ -5,10 +5,14 @@ defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do use Pleroma.Web.ConnCase + alias Pleroma.Config alias Pleroma.PasswordResetToken + alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.OAuth.Token import Pleroma.Factory + import Swoosh.TestAssertions describe "GET /api/pleroma/password_reset/token" do test "it returns error when token invalid", %{conn: conn} do @@ -116,4 +120,94 @@ test "it sets password_reset_pending to false", %{conn: conn} do assert User.get_by_id(user.id).password_reset_pending == false 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 empty_json_response(conn) + 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 nickname" do + test "it returns 204", %{conn: conn} do + user = insert(:user) + + assert conn + |> post("/auth/password?nickname=#{user.nickname}") + |> empty_json_response() + + 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 + + test "it doesn't fail when a user has no email", %{conn: conn} do + user = insert(:user, %{email: nil}) + + assert conn + |> post("/auth/password?nickname=#{user.nickname}") + |> empty_json_response() + end + end + + describe "POST /auth/password, with invalid parameters" do + setup do + user = insert(:user) + {:ok, user: user} + end + + test "it returns 204 when user is not found", %{conn: conn, user: user} do + conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") + + assert empty_json_response(conn) + end + + test "it returns 204 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 empty_json_response(conn) + end + + test "it returns 204 when user is deactivated", %{conn: conn, user: user} do + {:ok, user} = Repo.update(Ecto.Changeset.change(user, is_active: false, local: true)) + conn = post(conn, "/auth/password?email=#{user.email}") + + assert empty_json_response(conn) + end + end end