aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/generate_config.ex6
-rw-r--r--lib/mix/tasks/sample_config.eex12
-rw-r--r--lib/pleroma/application.ex3
-rw-r--r--lib/pleroma/notification.ex1
-rw-r--r--lib/pleroma/plugs/oauth_plug.ex3
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex29
-rw-r--r--lib/pleroma/web/mastodon_api/views/push_subscription_view.ex14
-rw-r--r--lib/pleroma/web/push/push.ex126
-rw-r--r--lib/pleroma/web/push/subscription.ex66
-rw-r--r--lib/pleroma/web/router.ex5
-rw-r--r--lib/pleroma/web/twitter_api/controllers/util_controller.ex6
11 files changed, 264 insertions, 7 deletions
diff --git a/lib/mix/tasks/generate_config.ex b/lib/mix/tasks/generate_config.ex
index e3cbbf131..be085d187 100644
--- a/lib/mix/tasks/generate_config.ex
+++ b/lib/mix/tasks/generate_config.ex
@@ -22,6 +22,8 @@ defmodule Mix.Tasks.GenerateConfig do
resultSql = EEx.eval_file("lib/mix/tasks/sample_psql.eex", dbpass: dbpass)
+ {web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
+
result =
EEx.eval_file(
"lib/mix/tasks/sample_config.eex",
@@ -29,7 +31,9 @@ defmodule Mix.Tasks.GenerateConfig do
email: email,
name: name,
secret: secret,
- dbpass: dbpass
+ dbpass: dbpass,
+ web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
+ web_push_private_key: Base.url_encode64(web_push_private_key, padding: false)
)
IO.puts(
diff --git a/lib/mix/tasks/sample_config.eex b/lib/mix/tasks/sample_config.eex
index 462c34636..47b6be729 100644
--- a/lib/mix/tasks/sample_config.eex
+++ b/lib/mix/tasks/sample_config.eex
@@ -25,6 +25,12 @@ config :pleroma, Pleroma.Repo,
hostname: "localhost",
pool_size: 10
+# Configure web push notifications
+config :web_push_encryption, :vapid_details,
+ subject: "mailto:<%= email %>",
+ public_key: "<%= web_push_public_key %>",
+ private_key: "<%= web_push_private_key %>"
+
# Enable Strict-Transport-Security once SSL is working:
# config :pleroma, :http_security,
# sts: true
@@ -50,9 +56,9 @@ config :pleroma, Pleroma.Repo,
# Configure Openstack Swift support if desired.
-#
-# Many openstack deployments are different, so config is left very open with
-# no assumptions made on which provider you're using. This should allow very
+#
+# Many openstack deployments are different, so config is left very open with
+# no assumptions made on which provider you're using. This should allow very
# wide support without needing separate handlers for OVH, Rackspace, etc.
#
# config :pleroma, Pleroma.Uploaders.Swift,
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 2d86efae5..d0f23527f 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -66,7 +66,8 @@ defmodule Pleroma.Application do
worker(Pleroma.Web.Federator, []),
worker(Pleroma.Web.Federator.RetryQueue, []),
worker(Pleroma.Gopher.Server, []),
- worker(Pleroma.Stats, [])
+ worker(Pleroma.Stats, []),
+ worker(Pleroma.Web.Push, [])
] ++
if @env == :test,
do: [],
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index a3aeb1221..a40b8f8c9 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -110,6 +110,7 @@ defmodule Pleroma.Notification do
notification = %Notification{user_id: user.id, activity: activity}
{:ok, notification} = Repo.insert(notification)
Pleroma.Web.Streamer.stream("user", notification)
+ Pleroma.Web.Push.send(notification)
notification
end
end
diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex
index 0380ce14d..651485e09 100644
--- a/lib/pleroma/plugs/oauth_plug.ex
+++ b/lib/pleroma/plugs/oauth_plug.ex
@@ -18,10 +18,11 @@ defmodule Pleroma.Plugs.OAuthPlug do
end
with token when not is_nil(token) <- token,
- %Token{user_id: user_id} <- Repo.get_by(Token, token: token),
+ %Token{user_id: user_id} = token <- Repo.get_by(Token, token: token),
%User{} = user <- Repo.get(User, user_id),
false <- !!user.info["deactivated"] do
conn
+ |> assign(:token, token)
|> assign(:user, user)
else
_ -> conn
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index aa7e9418e..f5b23971e 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1176,6 +1176,35 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
json(conn, %{})
end
+ alias Pleroma.Web.MastodonAPI.PushSubscriptionView
+
+ def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do
+ Pleroma.Web.Push.Subscription.delete_if_exists(user, token)
+ {:ok, subscription} = Pleroma.Web.Push.Subscription.create(user, token, params)
+ view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
+ json(conn, view)
+ end
+
+ def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
+ subscription = Pleroma.Web.Push.Subscription.get(user, token)
+ view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
+ json(conn, view)
+ end
+
+ def update_push_subscription(
+ %{assigns: %{user: user, token: token}} = conn,
+ params
+ ) do
+ {:ok, subscription} = Pleroma.Web.Push.Subscription.update(user, token, params)
+ view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
+ json(conn, view)
+ end
+
+ def delete_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
+ {:ok, _response} = Pleroma.Web.Push.Subscription.delete(user, token)
+ json(conn, %{})
+ end
+
def errors(conn, _) do
conn
|> put_status(500)
diff --git a/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex b/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex
new file mode 100644
index 000000000..a910bb43e
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex
@@ -0,0 +1,14 @@
+defmodule Pleroma.Web.MastodonAPI.PushSubscriptionView do
+ use Pleroma.Web, :view
+ alias Pleroma.Web.MastodonAPI.PushSubscriptionView
+
+ def render("push_subscription.json", %{subscription: subscription}) do
+ %{
+ id: to_string(subscription.id),
+ endpoint: subscription.endpoint,
+ alerts: Map.get(subscription.data, "alerts"),
+ # TODO: generate VAPID server key
+ server_key: "N/A"
+ }
+ end
+end
diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push/push.ex
new file mode 100644
index 000000000..d27750ab6
--- /dev/null
+++ b/lib/pleroma/web/push/push.ex
@@ -0,0 +1,126 @@
+defmodule Pleroma.Web.Push do
+ use GenServer
+
+ alias Pleroma.{Repo, User}
+ alias Pleroma.Web.Push.Subscription
+
+ require Logger
+ import Ecto.Query
+
+ @types ["Create", "Follow", "Announce", "Like"]
+
+ @gcm_api_key nil
+
+ def start_link() do
+ GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
+ end
+
+ def init(:ok) do
+ case Application.get_env(:web_push_encryption, :vapid_details) do
+ nil ->
+ Logger.error(
+ "VAPID key pair is not found. Please, add VAPID configuration to config. Run `mix web_push.gen.keypair` mix task to create a key pair"
+ )
+
+ {:error, %{}}
+
+ _ ->
+ {:ok, %{}}
+ end
+ end
+
+ def send(notification) do
+ GenServer.cast(Pleroma.Web.Push, {:send, notification})
+ end
+
+ def handle_cast(
+ {:send, %{activity: %{data: %{"type" => type}}, user_id: user_id} = notification},
+ state
+ )
+ when type in @types do
+ actor = User.get_cached_by_ap_id(notification.activity.data["actor"])
+ body = notification |> format(actor) |> Jason.encode!()
+
+ Subscription
+ |> where(user_id: ^user_id)
+ |> Repo.all()
+ |> Enum.each(fn record ->
+ subscription = %{
+ keys: %{
+ p256dh: record.key_p256dh,
+ auth: record.key_auth
+ },
+ endpoint: record.endpoint
+ }
+
+ case WebPushEncryption.send_web_push(body, subscription, @gcm_api_key) do
+ {:ok, %{status_code: code}} when 400 <= code and code < 500 ->
+ Logger.debug("Removing subscription record")
+ Repo.delete!(record)
+ :ok
+
+ {:ok, %{status_code: code}} when 200 <= code and code < 300 ->
+ :ok
+
+ {:ok, %{status_code: code}} ->
+ Logger.error("Web Push Nonification failed with code: #{code}")
+ :error
+
+ data ->
+ Logger.error("Web Push Nonification failed with unknown error")
+ IO.inspect(data)
+ :error
+ end
+ end)
+
+ {:noreply, state}
+ end
+
+ def handle_cast({:send, _}, state) do
+ Logger.warn("Unknown notification type")
+ {:noreply, state}
+ end
+
+ def format(%{activity: %{data: %{"type" => "Create"}}}, actor) do
+ %{
+ title: "New Mention",
+ body: "@#{actor.nickname} has mentiond you",
+ icon: get_avatar_url(actor)
+ }
+ end
+
+ def format(%{activity: %{data: %{"type" => "Follow"}}}, actor) do
+ %{
+ title: "New Follower",
+ body: "@#{actor.nickname} has followed you",
+ icon: get_avatar_url(actor)
+ }
+ end
+
+ def format(%{activity: %{data: %{"type" => "Announce"}}}, actor) do
+ %{
+ title: "New Announce",
+ body: "@#{actor.nickname} has announced your post",
+ icon: get_avatar_url(actor)
+ }
+ end
+
+ def format(%{activity: %{data: %{"type" => "Like"}}}, actor) do
+ %{
+ title: "New Like",
+ body: "@#{actor.nickname} has liked your post",
+ icon: get_avatar_url(actor)
+ }
+ end
+
+ def get_avatar_url(%{avatar: %{"type" => "Image", "url" => urls}}) do
+ case List.first(urls) do
+ %{"href" => url} -> url
+ _ -> get_avatar_url(nil)
+ end
+ end
+
+ def get_avatar_url(_) do
+ Pleroma.Web.Endpoint.static_url() <> "/images/avi.png"
+ end
+end
diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex
new file mode 100644
index 000000000..cfab7a98e
--- /dev/null
+++ b/lib/pleroma/web/push/subscription.ex
@@ -0,0 +1,66 @@
+defmodule Pleroma.Web.Push.Subscription do
+ use Ecto.Schema
+ import Ecto.Changeset
+ alias Pleroma.{Repo, User}
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.Push.Subscription
+
+ schema "push_subscriptions" do
+ belongs_to(:user, User)
+ belongs_to(:token, Token)
+ field(:endpoint, :string)
+ field(:key_p256dh, :string)
+ field(:key_auth, :string)
+ field(:data, :map, default: %{})
+
+ timestamps()
+ end
+
+ @supported_alert_types ~w[follow favourite mention reblog]
+
+ defp alerts(%{"data" => %{"alerts" => alerts}}) do
+ alerts = Map.take(alerts, @supported_alert_types)
+ %{"alerts" => alerts}
+ end
+
+ def create(
+ %User{} = user,
+ %Token{} = token,
+ %{
+ "subscription" => %{
+ "endpoint" => endpoint,
+ "keys" => %{"auth" => key_auth, "p256dh" => key_p256dh}
+ }
+ } = params
+ ) do
+ Repo.insert(%Subscription{
+ user_id: user.id,
+ token_id: token.id,
+ endpoint: endpoint,
+ key_auth: key_auth,
+ key_p256dh: key_p256dh,
+ data: alerts(params)
+ })
+ end
+
+ def get(%User{id: user_id}, %Token{id: token_id}) do
+ Repo.get_by(Subscription, user_id: user_id, token_id: token_id)
+ end
+
+ def update(user, token, params) do
+ get(user, token)
+ |> change(data: alerts(params))
+ |> Repo.update()
+ end
+
+ def delete(user, token) do
+ Repo.delete(get(user, token))
+ end
+
+ def delete_if_exists(user, token) do
+ case get(user, token) do
+ nil -> {:ok, nil}
+ sub -> Repo.delete(sub)
+ end
+ end
+end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 09265954a..d8af81992 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -198,6 +198,11 @@ defmodule Pleroma.Web.Router do
put("/filters/:id", MastodonAPIController, :update_filter)
delete("/filters/:id", MastodonAPIController, :delete_filter)
+ post("/push/subscription", MastodonAPIController, :create_push_subscription)
+ get("/push/subscription", MastodonAPIController, :get_push_subscription)
+ put("/push/subscription", MastodonAPIController, :update_push_subscription)
+ delete("/push/subscription", MastodonAPIController, :delete_push_subscription)
+
get("/suggestions", MastodonAPIController, :suggestions)
get("/endorsements", MastodonAPIController, :empty_array)
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index b0ed8387e..092779010 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -157,13 +157,17 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|> send_resp(200, response)
_ ->
+ vapid_public_key =
+ Keyword.get(Application.get_env(:web_push_encryption, :vapid_details), :public_key)
+
data = %{
name: Keyword.get(instance, :name),
description: Keyword.get(instance, :description),
server: Web.base_url(),
textlimit: to_string(Keyword.get(instance, :limit)),
closed: if(Keyword.get(instance, :registrations_open), do: "0", else: "1"),
- private: if(Keyword.get(instance, :public, true), do: "0", else: "1")
+ private: if(Keyword.get(instance, :public, true), do: "0", else: "1"),
+ vapidPublicKey: vapid_public_key
}
pleroma_fe = %{