Add anonymous and remote participations

This commit is contained in:
Thomas Citharel
2019-12-20 13:04:34 +01:00
parent 17e0b3968f
commit 2ed9050a90
135 changed files with 10141 additions and 2271 deletions

View File

@@ -2,7 +2,7 @@ defmodule Mobilizon.Web.GraphQLSocket do
use Phoenix.Socket
use Absinthe.Phoenix.Socket,
schema: Mobilizon.Web.Schema
schema: Mobilizon.GraphQL.Schema
alias Mobilizon.Users.User

View File

@@ -4,6 +4,9 @@ defmodule Mobilizon.Web.PageController do
"""
use Mobilizon.Web, :controller
alias Mobilizon.Events.{Comment, Event}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Tombstone
alias Mobilizon.Web.Cache
plug(:put_layout, false)
@@ -13,40 +16,65 @@ defmodule Mobilizon.Web.PageController do
def actor(conn, %{"name" => name}) do
{status, actor} = Cache.get_local_actor_by_name(name)
render_or_error(conn, &ok_status?/2, status, :actor, actor)
render_or_error(conn, &ok_status?/3, status, :actor, actor)
end
def event(conn, %{"uuid" => uuid}) do
{status, event} = Cache.get_public_event_by_uuid_with_preload(uuid)
render_or_error(conn, &ok_status_and_is_visible?/2, status, :event, event)
render_or_error(conn, &checks?/3, status, :event, event)
end
def comment(conn, %{"uuid" => uuid}) do
{status, comment} = Cache.get_comment_by_uuid_with_preload(uuid)
render_or_error(conn, &ok_status_and_is_visible?/2, status, :comment, comment)
render_or_error(conn, &checks?/3, status, :comment, comment)
end
def interact(conn, %{"uri" => uri}) do
case ActivityPub.fetch_object_from_url(uri) do
{:ok, %Event{uuid: uuid}} -> redirect(conn, to: "/events/#{uuid}")
{:ok, %Comment{uuid: uuid}} -> redirect(conn, to: "/comments/#{uuid}")
_ -> {:error, :not_found}
end
end
defp render_or_error(conn, check_fn, status, object_type, object) do
if check_fn.(status, object) do
case object do
%Mobilizon.Tombstone{} ->
conn
|> put_status(:gone)
|> render(object_type, object: object)
case check_fn.(conn, status, object) do
true ->
case object do
%Tombstone{} ->
conn
|> put_status(:gone)
|> render(object_type, object: object)
_ ->
render(conn, object_type, object: object)
end
else
{:error, :not_found}
_ ->
render(conn, object_type, object: object)
end
:remote ->
redirect(conn, external: object.url)
false ->
{:error, :not_found}
end
end
defp is_visible?(%{visibility: v}), do: v in [:public, :unlisted]
defp is_visible?(%Mobilizon.Tombstone{}), do: true
defp is_visible?(%Tombstone{}), do: true
defp ok_status?(status), do: status in [:ok, :commit]
defp ok_status?(status, _), do: ok_status?(status)
defp ok_status?(_conn, status, _), do: ok_status?(status)
defp ok_status_and_is_visible?(status, o), do: ok_status?(status) and is_visible?(o)
defp ok_status_and_is_visible?(_conn, status, o),
do: ok_status?(status) and is_visible?(o)
defp checks?(conn, status, o) do
if ok_status_and_is_visible?(conn, status, o) do
if is_local?(o) == :remote && get_format(conn) == "activity-json", do: :remote, else: true
else
false
end
end
defp is_local?(%Event{local: local}), do: if(local, do: true, else: :remote)
defp is_local?(%Comment{local: local}), do: if(local, do: true, else: :remote)
end

View File

@@ -10,4 +10,19 @@ defmodule Mobilizon.Web.Email.Checker do
"""
@spec valid?(String.t()) :: boolean
def valid?(email), do: email =~ @email_regex
@spec validate_changeset(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
def validate_changeset(%Ecto.Changeset{} = changeset, key \\ :email) do
changeset = Ecto.Changeset.validate_length(changeset, :email, min: 3, max: 250)
case Ecto.Changeset.fetch_change(changeset, key) do
{:ok, email} ->
if valid?(email),
do: changeset,
else: Ecto.Changeset.add_error(changeset, :email, "Email doesn't fit required format")
:error ->
changeset
end
end
end

View File

@@ -2,26 +2,34 @@ defmodule Mobilizon.Web.Email.Participation do
@moduledoc """
Handles emails sent about participation.
"""
use Bamboo.Phoenix, view: Mobilizon.Web.EmailView
import Bamboo.Phoenix
import Mobilizon.Web.Gettext
alias Mobilizon.Actors.Actor
alias Mobilizon.Config
alias Mobilizon.Events.Participant
alias Mobilizon.Users
alias Mobilizon.Users.User
alias Mobilizon.Web.{Email, Gettext}
@doc """
Send emails to local user
"""
def send_emails_to_local_user(
%Participant{actor: %Actor{user_id: nil} = _actor} = _participation
),
do: :ok
%Participant{actor: %Actor{user_id: nil, id: actor_id} = _actor} = participation
) do
if actor_id == Config.anonymous_actor_id() do
%{email: email} = Map.get(participation, :metadata)
email
|> participation_updated(participation)
|> Email.Mailer.deliver_later()
end
:ok
end
@doc """
Send emails to local user
@@ -29,7 +37,7 @@ defmodule Mobilizon.Web.Email.Participation do
def send_emails_to_local_user(
%Participant{actor: %Actor{user_id: user_id} = _actor} = participation
) do
with %User{} = user <- Mobilizon.Users.get_user!(user_id) do
with %User{} = user <- Users.get_user!(user_id) do
user
|> participation_updated(participation)
|> Email.Mailer.deliver_later()
@@ -38,11 +46,21 @@ defmodule Mobilizon.Web.Email.Participation do
end
end
@spec participation_updated(User.t(), Participant.t(), String.t()) :: Bamboo.Email.t()
@spec participation_updated(String.t() | User.t(), Participant.t(), String.t()) ::
Bamboo.Email.t()
def participation_updated(user, participant, locale \\ "en")
@spec participation_updated(User.t(), Participant.t(), String.t()) :: Bamboo.Email.t()
def participation_updated(
%User{email: email},
%Participant{} = participant,
locale
),
do: participation_updated(email, participant, locale)
@spec participation_updated(String.t(), Participant.t(), String.t()) :: Bamboo.Email.t()
def participation_updated(
email,
%Participant{event: event, role: :rejected},
locale
) do
@@ -61,9 +79,9 @@ defmodule Mobilizon.Web.Email.Participation do
|> render(:event_participation_rejected)
end
@spec participation_updated(User.t(), Participant.t(), String.t()) :: Bamboo.Email.t()
@spec participation_updated(String.t(), Participant.t(), String.t()) :: Bamboo.Email.t()
def participation_updated(
%User{email: email},
email,
%Participant{event: event, role: :participant},
locale
) do
@@ -81,4 +99,26 @@ defmodule Mobilizon.Web.Email.Participation do
|> assign(:subject, subject)
|> render(:event_participation_approved)
end
@spec anonymous_participation_confirmation(String.t(), Participant.t(), String.t()) ::
Bamboo.Email.t()
def anonymous_participation_confirmation(
email,
%Participant{event: event, role: :not_confirmed} = participant,
locale \\ "en"
) do
Gettext.put_locale(locale)
subject =
gettext(
"Confirm your participation to event %{title}",
title: event.title
)
Email.base_email(to: email, subject: subject)
|> assign(:locale, locale)
|> assign(:participant, participant)
|> assign(:subject, subject)
|> render(:anonymous_participation_confirmation)
end
end

View File

@@ -58,8 +58,6 @@ defmodule Mobilizon.Web.Router do
)
end
## FEDERATION
scope "/.well-known", Mobilizon.Web do
pipe_through(:well_known)
@@ -110,8 +108,10 @@ defmodule Mobilizon.Web.Router do
end
## MOBILIZON
forward("/graphiql", Absinthe.Plug.GraphiQL, schema: Mobilizon.Web.Schema)
scope "/graphiql" do
pipe_through(:graphql)
forward("/", Absinthe.Plug.GraphiQL, schema: Mobilizon.GraphQL.Schema)
end
scope "/", Mobilizon.Web do
pipe_through(:browser)
@@ -125,6 +125,12 @@ defmodule Mobilizon.Web.Router do
# This is a hack to ease link generation into emails
get("/moderation/reports/:id", PageController, :index, as: "moderation_report")
get("/participation/email/confirm/:token", PageController, :index,
as: "participation_email_confirmation"
)
get("/interact", PageController, :interact)
end
scope "/proxy/", Mobilizon.Web do

View File

@@ -0,0 +1,163 @@
<h3><%= pgettext("terms", "What information do we collect?") %></h3>
<ul>
<li>
<em><%= pgettext("terms", "Basic account information") %></em>
<p><%= pgettext(
"terms",
"We collect information from you when you register on this server and gather data when you participate in the
platform by reading, writing, and interacting with content shared here. If you register on this server, you will
be asked to enter an e-mail address, a password and at least an username. Your e-mail address will be verified by
an email containing a unique link. If that link is visited, we know that you control the e-mail address. You may
also enter additional profile information such as a display name and biography, and upload a profile picture and
header image. The username, display name, biography, profile picture and header image are always listed publicly.
You may, however, visit this server without registering."
) %>
</p>
</li>
<li>
<em><%= pgettext("terms", "Published events and comments") %></em>
<p>
<%= pgettext(
"terms",
"Your events and comments are delivered to other instances that follow your own, meaning they are delivered to
different servers and copies are stored there. When you delete events or comments, this is likewise delivered to
these other instances. The action of joining an event is federated as well. Please keep in mind that the operators
of the server and any receiving server may view such messages, and that recipients may screenshot, copy or
otherwise re-share them."
) %>
<em><%= pgettext("terms", "Do not share any dangerous information over Mobilizon.") %></em>
</p>
</li>
<li>
<em><%= pgettext("terms", "IPs and other metadata") %></em>
<p>
<%=
pgettext(
"terms",
"We also may retain server logs which include the IP address of every request to our server."
)
%>
</p>
</li>
</ul>
<h3><%= pgettext("terms", "What do we use your information for?") %></h3>
<p><%=
pgettext(
"terms",
"Any of the information we collect from you may be used in the following ways:"
)
%></p>
<ul>
<li><%= pgettext("terms", "To provide the core functionality of Mobilizon. Depending on this instance's policy you may only be able to
interact with other people's content and post your own content if you are logged in.") %></li>
<li><%= pgettext("terms", "To aid moderation of the community, for example comparing your IP address with other known ones to determine ban
evasion or other violations.") %></li>
<li><%= pgettext("terms", "The email address you provide may be used to send you information, updates and notifications about other people
interacting with your content or sending you messages and to respond to inquiries, and/or other requests or
questions.") %></li>
</ul>
<h3 class="title"><%= pgettext("terms", "How do we protect your information?") %></h3>
<p>
<%=
pgettext(
"terms",
"We implement a variety of security measures to maintain the safety of your personal information when you enter,
submit, or access your personal information. Among other things, your browser session, as well as the traffic between
your applications and the API, are secured with SSL/TLS, and your password is hashed using a strong one-way
algorithm."
)
%>
</p>
<h3 class="title"><%= pgettext("terms", "What is our data retention policy?") %></h3>
<p><%= pgettext("terms", "We will make a good faith effort to:") %></p>
<ul>
<li><%=
pgettext(
"terms",
"Retain server logs containing the IP address of all requests to this server, in so far as such logs are kept, no more
than 90 days."
)%>
</li>
<li>
<%=
pgettext(
"terms",
"Retain the IP addresses associated with registered users no more than 12 months."
)
%>
</li>
</ul>
<p>
<%=
pgettext(
"terms",
"You can request and download an archive of your content, including your posts, media attachments, profile picture,
and header image."
)
%>
</p>
<p><%= pgettext("terms", "You may irreversibly delete your account at any time.") %></p>
<h3 class="title"><%= pgettext("terms", "Do we use cookies?") %></h3>
<p><%=
pgettext("terms", "We store the following information on your device when you connect:")
%>
</p>
<ul>
<li><%= pgettext("terms", "An internal user ID") %></li>
<li><%= pgettext("terms", "An internal ID for your current selected identity") %></li>
<li><%= pgettext("terms", "Tokens to authenticate you") %></li>
</ul>
<p><%= pgettext("terms", "If you delete these informations, you need to login again.") %></p>
<p><%=
pgettext(
"terms",
"If you're not connected, we don't store any information on your device, unless you participate in an event
anonymously. In that case we store the hash of the UUID and participation status in your browser so that we may
display participation status. Deleting these informations will only stop displaying participation status in your
browser."
)
%>
</p>
<em>
<%= pgettext("terms", "Note: These informations are stored in your localStorage and not your cookies.") %>
</em>
<h3 class="title"><%=
pgettext("terms", "Do we disclose any information to outside parties?")
%></h3>
<p>
<%=
pgettext(
"terms",
"We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This
does not include trusted third parties who assist us in operating our site, conducting our business, or servicing
you, so long as those parties agree to keep this information confidential. We may also release your information
when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or
others rights, property, or safety."
)
%>
</p>
<p>
<%=
pgettext(
"terms",
"Your content may be downloaded by other servers in the network. Your content is delivered to the servers
following your instance, and direct messages are delivered to the servers of the recipients, in so far as these
recipients reside on a different server than this one."
)
%>
</p>
<h3 class="title"><%=
pgettext("terms", "Site usage by children")
%></h3>
<p><%= pgettext("terms", "If this server is in the EU or the EEA: Our site, products and services are all directed to people who are at least 16 years old. If you are under the age of 16, per the requirements of the GDPR (<a href=\"https://en.wikipedia.org/wiki/General_Data_Protection_Regulation\">General Data Protection Regulation</a>) do not use this site.") |> raw %></p>
<p><%= pgettext("terms", "If this server is in the USA: Our site, products and services are all directed to people who are at least 13 years old. If you are under the age of 13, per the requirements of COPPA (<a href=\"https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act\">Children's Online Privacy Protection Act</a>) do not use this site.") |> raw %></p>
<p><%= pgettext("terms", "Law requirements can be different if this server is in another jurisdiction.") %></p>
<h3 class="title"><%=
pgettext("terms", "Changes to our Privacy Policy")
%></h3>
<p><%= pgettext("terms", "If we decide to change our privacy policy, we will post those changes on this page.") %></p>
<p><%= pgettext("terms", "This document is CC-BY-SA. It was last updated January 16, 2020.") %></p>
<p><%= pgettext("terms", "Originally adapted from the <a href=\"https://mastodon.social/terms\">Mastodon</a> and <a href=\"https://github.com/discourse/discourse\">Discourse</a> privacy policies.") |> raw %></p>

View File

@@ -0,0 +1,81 @@
<!-- HERO -->
<tr>
<td bgcolor="#424056" align="center" style="padding: 0px 10px 0px 10px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">
<%= gettext "Participation confirmation" %>
</h1>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- COPY BLOCK -->
<tr>
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 10px 0px 10px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<!-- COPY -->
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 0px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0;">
<%= gettext "You requested to participate in event %{title}", title: @participant.event.title %>
</p>
</td>
</tr>
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #777777; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0">
<%= gettext "If you didn't request this email, you can simply ignore it." %>
</p>
</td>
</tr>
<!-- BULLETPROOF BUTTON -->
<tr>
<td bgcolor="#ffffff" align="left">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 20px 30px 60px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="border-radius: 3px;" bgcolor="#424056"><a href="<%= participation_email_confirmation_url(Mobilizon.Web.Endpoint, :index, @participant.metadata.confirmation_token) %>" target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #424056; display: inline-block;">
<%= gettext "Confirm my participation" %>
</a></td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #777777; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 20px;" >
<p style="margin: 0">
<%= gettext "If you need to cancel your participation, just access the event page through link above and click on the participation button." %>
</p>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>

View File

@@ -0,0 +1,11 @@
<%= gettext "Participation confirmation" %>
==
<%= gettext "You requested to participate in event %{title}.", title: @participant.event.title %>
<%= gettext "If you didn't request this email, you can simply ignore it." %>
<%= participation_email_confirmation_url(Mobilizon.Web.Endpoint, :index, @participant.metadata.confirmation_token) %>
<%= gettext "If you need to cancel your participation, just access the previous link and click on the participation button." %>

View File

@@ -8,7 +8,7 @@
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">
<%= gettext "All good!" %>
</h1>

View File

@@ -8,7 +8,7 @@
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">
<%= gettext "Sorry!" %>
</h1>

View File

@@ -8,7 +8,7 @@
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">
<%= gettext "Event updated!" %>
</h1>

View File

@@ -8,7 +8,7 @@
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">
<%= gettext "Trouble signing in?" %>
</h1>

View File

@@ -8,7 +8,7 @@
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">
<%= gettext "Nearly here!" %>
</h1>

View File

@@ -8,7 +8,7 @@
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; letter-spacing: 4px; line-height: 48px;">
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">
<%= gettext "New report on %{instance}", instance: @instance[:name] %>
</h1>

View File

@@ -0,0 +1,7 @@
defmodule Mobilizon.Web.APIView do
@moduledoc """
View for our the API terms
"""
use Mobilizon.Web, :view
import Mobilizon.Web.Gettext
end