# Pleroma: A lightweight social networking server # Copyright © 2017-2023 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Push.Subscription do use Ecto.Schema import Ecto.Changeset alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.OAuth.Token alias Pleroma.Web.Push.Subscription @type t :: %__MODULE__{} schema "push_subscriptions" do belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:token, Token) field(:endpoint, :string) field(:key_p256dh, :string) field(:key_auth, :string) field(:data, :map, default: %{}) timestamps() end # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength @supported_alert_types ~w[follow favourite mention reblog poll pleroma:chat_mention pleroma:emoji_reaction]a defp alerts(%{data: %{alerts: alerts}}) do alerts = Map.take(alerts, @supported_alert_types) %{"alerts" => alerts} end def enabled?(subscription, "follow_request") do enabled?(subscription, "follow") end def enabled?(subscription, alert_type) do get_in(subscription.data, ["alerts", alert_type]) 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: ensure_base64_urlsafe(key_auth), key_p256dh: ensure_base64_urlsafe(key_p256dh), data: alerts(params) }) end @doc "Gets subsciption by user & token" @spec get(User.t(), Token.t()) :: {:ok, t()} | {:error, :not_found} def get(%User{id: user_id}, %Token{id: token_id}) do case Repo.get_by(Subscription, user_id: user_id, token_id: token_id) do nil -> {:error, :not_found} subscription -> {:ok, subscription} end end def update(user, token, params) do with {:ok, subscription} <- get(user, token) do subscription |> change(data: alerts(params)) |> Repo.update() end end def delete(user, token) do with {:ok, subscription} <- get(user, token), do: Repo.delete(subscription) end def delete_if_exists(user, token) do case get(user, token) do {:error, _} -> {:ok, nil} {:ok, sub} -> Repo.delete(sub) end end # Some webpush clients (e.g. iOS Toot!) use an non urlsafe base64 as an encoding for the key. # However, the web push rfs specify to use base64 urlsafe, and the `web_push_encryption` library # we use requires the key to be properly encoded. So we just convert base64 to urlsafe base64. defp ensure_base64_urlsafe(string) do string |> String.replace("+", "-") |> String.replace("/", "_") |> String.replace("=", "") end end