Commit 03a71eba authored by hakabahitoyo's avatar hakabahitoyo
Browse files

Merge remote-tracking branch 'official/develop' into deploy/distsn/clean-config

parents 05793250 005b4194
......@@ -39,7 +39,7 @@ While we don't provide docker files, other people have written very good ones. T
* Run `mix pleroma.instance gen`. This will ask you questions about your instance and generate a configuration file in `config/generated_config.exs`. Check that and copy it to either `config/dev.secret.exs` or `config/prod.secret.exs`. It will also create a `config/setup_db.psql`, which you should run as the PostgreSQL superuser (i.e., `sudo -u postgres psql -f config/setup_db.psql`). It will create the database, user, and password you gave `mix pleroma.gen.instance` earlier, as well as set up the necessary extensions in the database. PostgreSQL superuser privileges are only needed for this step.
* For these next steps, the default will be to run pleroma using the dev configuration file, `config/dev.secret.exs`. To run them using the prod config file, prefix each command at the shell with `MIX_ENV=prod`. For example: `MIX_ENV=prod mix phx.server`. Documentation for the config can be found at [``config/config.md``](config/config.md)
* For these next steps, the default will be to run pleroma using the dev configuration file, `config/dev.secret.exs`. To run them using the prod config file, prefix each command at the shell with `MIX_ENV=prod`. For example: `MIX_ENV=prod mix phx.server`. Documentation for the config can be found at [``docs/config.md``](docs/config.md)
* Run `mix ecto.migrate` to run the database migrations. You will have to do this again after certain updates.
......
......@@ -163,6 +163,8 @@
allow_followersonly: false,
allow_direct: false
config :pleroma, :mrf_hellthread, threshold: 10
config :pleroma, :mrf_simple,
media_removal: [],
media_nsfw: [],
......
......@@ -69,6 +69,7 @@ config :pleroma, Pleroma.Mailer,
* `banner_upload_limit`: File size limit of user’s profile banners
* `registrations_open`: Enable registrations for anyone, invitations can be enabled when false.
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
* `account_activation_required`: Require users to confirm their emails before signing in.
* `federating`: Enable federation with other instances
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance
* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
......@@ -120,6 +121,9 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
* `allow_followersonly`: whether to allow followers-only posts
* `allow_direct`: whether to allow direct messages
## :mrf_hellthread
* `threshold`: Number of mentioned users after which the message gets discarded as spam
## :media_proxy
* `enabled`: Enables proxying of remote media to the instance’s proxy
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
......
......@@ -103,8 +103,8 @@ def run(["new", nickname, email | rest]) do
bio: bio
}
user = User.register_changeset(%User{}, params)
Repo.insert!(user)
changeset = User.register_changeset(%User{}, params, confirmed: true)
{:ok, _user} = User.register(changeset)
Mix.shell().info("User #{nickname} created")
......
......@@ -15,6 +15,7 @@ defp sender do
defp recipient(email, nil), do: email
defp recipient(email, name), do: {name, email}
defp recipient(%Pleroma.User{} = user), do: recipient(user.email, user.name)
def password_reset_email(user, password_reset_token) when is_binary(password_reset_token) do
password_reset_url =
......@@ -32,7 +33,7 @@ def password_reset_email(user, password_reset_token) when is_binary(password_res
"""
new()
|> to(recipient(user.email, user.name))
|> to(recipient(user))
|> from(sender())
|> subject("Password reset")
|> html_body(html_body)
......@@ -63,4 +64,26 @@ def user_invitation_email(
|> subject("Invitation to #{instance_name()}")
|> html_body(html_body)
end
def account_confirmation_email(user) do
confirmation_url =
Router.Helpers.confirm_email_url(
Endpoint,
:confirm_email,
user.id,
to_string(user.info.confirmation_token)
)
html_body = """
<h3>Welcome to #{instance_name()}!</h3>
<p>Email confirmation is required to activate the account.</p>
<p>Click the following link to proceed: <a href="#{confirmation_url}">activate your account</a>.</p>
"""
new()
|> to(recipient(user))
|> from(sender())
|> subject("#{instance_name()} account confirmation")
|> html_body(html_body)
end
end
......@@ -7,6 +7,9 @@ defmodule Pleroma.Formatter do
@tag_regex ~r/((?<=[^&])|\A)(\#)(\w+)/u
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
# Modified from https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
@mentions_regex ~r/@[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]*@?[a-zA-Z0-9_-](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/u
def parse_tags(text, data \\ %{}) do
Regex.scan(@tag_regex, text)
|> Enum.map(fn ["#" <> tag = full_tag | _] -> {full_tag, String.downcase(tag)} end)
......@@ -17,16 +20,15 @@ def parse_tags(text, data \\ %{}) do
end).()
end
@doc "Parses mentions text and returns list {nickname, user}."
@spec parse_mentions(binary()) :: list({binary(), User.t()})
def parse_mentions(text) do
# Modified from https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
regex =
~r/@[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]*@?[a-zA-Z0-9_-](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/u
Regex.scan(regex, text)
Regex.scan(@mentions_regex, text)
|> List.flatten()
|> Enum.uniq()
|> Enum.map(fn "@" <> match = full_match ->
{full_match, User.get_cached_by_nickname(match)}
|> Enum.map(fn nickname ->
with nickname <- String.trim_leading(nickname, "@"),
do: {"@" <> nickname, User.get_cached_by_nickname(nickname)}
end)
|> Enum.filter(fn {_match, user} -> user end)
end
......
......@@ -38,6 +38,13 @@ defmodule Pleroma.User do
timestamps()
end
def auth_active?(%User{} = user) do
(user.info && !user.info.confirmation_pending) ||
!Pleroma.Config.get([:instance, :account_activation_required])
end
def superuser?(%User{} = user), do: user.info && User.Info.superuser?(user.info)
def avatar_url(user) do
case user.avatar do
%{"url" => [%{"href" => href} | _]} -> href
......@@ -78,6 +85,7 @@ def user_info(%User{} = user) do
note_count: user.info.note_count,
follower_count: user.info.follower_count,
locked: user.info.locked,
confirmation_pending: user.info.confirmation_pending,
default_scope: user.info.default_scope
}
end
......@@ -168,7 +176,16 @@ def reset_password(user, data) do
update_and_set_cache(password_update_changeset(user, data))
end
def register_changeset(struct, params \\ %{}) do
def register_changeset(struct, params \\ %{}, opts \\ []) do
confirmation_status =
if opts[:confirmed] || !Pleroma.Config.get([:instance, :account_activation_required]) do
:confirmed
else
:unconfirmed
end
info_change = User.Info.confirmation_changeset(%User.Info{}, confirmation_status)
changeset =
struct
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
......@@ -180,7 +197,7 @@ def register_changeset(struct, params \\ %{}) do
|> validate_format(:email, @email_regex)
|> validate_length(:bio, max: 1000)
|> validate_length(:name, min: 1, max: 100)
|> put_change(:info, %Pleroma.User.Info{})
|> put_change(:info, info_change)
if changeset.valid? do
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
......@@ -197,6 +214,25 @@ def register_changeset(struct, params \\ %{}) do
end
end
@doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
def register(%Ecto.Changeset{} = changeset) do
with {:ok, user} <- Repo.insert(changeset),
{:ok, _} = try_send_confirmation_email(user) do
{:ok, user}
end
end
def try_send_confirmation_email(%User{} = user) do
if user.info.confirmation_pending &&
Pleroma.Config.get([:instance, :account_activation_required]) do
user
|> Pleroma.UserEmail.account_confirmation_email()
|> Pleroma.Mailer.deliver()
else
{:ok, :noop}
end
end
def needs_update?(%User{local: true}), do: false
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
......
......@@ -9,6 +9,8 @@ defmodule Pleroma.User.Info do
field(:note_count, :integer, default: 0)
field(:follower_count, :integer, default: 0)
field(:locked, :boolean, default: false)
field(:confirmation_pending, :boolean, default: false)
field(:confirmation_token, :string, default: nil)
field(:default_scope, :string, default: "public")
field(:blocks, {:array, :string}, default: [])
field(:domain_blocks, {:array, :string}, default: [])
......@@ -35,6 +37,8 @@ defmodule Pleroma.User.Info do
# subject _> Where is this used?
end
def superuser?(info), do: info.is_admin || info.is_moderator
def set_activation_status(info, deactivated) do
params = %{deactivated: deactivated}
......@@ -141,6 +145,24 @@ def profile_update(info, params) do
])
end
def confirmation_changeset(info, :confirmed) do
confirmation_changeset(info, %{
confirmation_pending: false,
confirmation_token: nil
})
end
def confirmation_changeset(info, :unconfirmed) do
confirmation_changeset(info, %{
confirmation_pending: true,
confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
})
end
def confirmation_changeset(info, params) do
cast(info, params, [:confirmation_pending, :confirmation_token])
end
def mastodon_profile_update(info, params) do
info
|> cast(params, [
......
defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF
@impl true
def filter(%{"type" => "Create"} = object) do
threshold = Pleroma.Config.get([:mrf_hellthread, :threshold])
recipients = (object["to"] || []) ++ (object["cc"] || [])
if length(recipients) > threshold do
{:reject, nil}
else
{:ok, object}
end
end
@impl true
def filter(object), do: {:ok, object}
end
......@@ -69,8 +69,8 @@ def contain_origin_from_id(id, %{"id" => other_id} = _params) do
def fix_object(object) do
object
|> fix_actor
|> fix_attachments
|> fix_url
|> fix_attachments
|> fix_context
|> fix_in_reply_to
|> fix_emoji
......@@ -170,8 +170,14 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
attachments =
attachment
|> Enum.map(fn data ->
url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}]
Map.put(data, "url", url)
media_type = data["mediaType"] || data["mimeType"]
href = data["url"] || data["href"]
url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}]
data
|> Map.put("mediaType", media_type)
|> Map.put("url", url)
end)
object
......@@ -190,7 +196,22 @@ def fix_url(%{"url" => url} = object) when is_map(url) do
|> Map.put("url", url["href"])
end
def fix_url(%{"url" => url} = object) when is_list(url) do
def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do
first_element = Enum.at(url, 0)
link_element =
url
|> Enum.filter(fn x -> is_map(x) end)
|> Enum.filter(fn x -> x["mimeType"] == "text/html" end)
|> Enum.at(0)
object
|> Map.put("attachment", [first_element])
|> Map.put("url", link_element["href"])
end
def fix_url(%{"type" => object_type, "url" => url} = object)
when object_type != "Video" and is_list(url) do
first_element = Enum.at(url, 0)
url_string =
......
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
use Pleroma.Web, :controller
alias Pleroma.{User, Repo}
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
......@@ -26,7 +26,7 @@ def user_create(
conn,
%{"nickname" => nickname, "email" => email, "password" => password}
) do
new_user = %{
user_data = %{
nickname: nickname,
name: nickname,
email: email,
......@@ -35,11 +35,11 @@ def user_create(
bio: "."
}
User.register_changeset(%User{}, new_user)
|> Repo.insert!()
changeset = User.register_changeset(%User{}, user_data, confirmed: true)
{:ok, user} = User.register(changeset)
conn
|> json(new_user.nickname)
|> json(user.nickname)
end
def tag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do
......
......@@ -110,7 +110,8 @@ def verify_credentials(%{assigns: %{user: user}} = conn, _) do
end
def user(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do
with %User{} = user <- Repo.get(User, id) do
with %User{} = user <- Repo.get(User, id),
true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do
account = AccountView.render("account.json", %{user: user, for: for_user})
json(conn, account)
else
......
......@@ -62,6 +62,7 @@ def render("account.json", %{user: user} = opts) do
# Pleroma extension
pleroma: %{
confirmation_pending: user_info.confirmation_pending,
tags: user.tags
}
}
......
......@@ -106,7 +106,6 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
attachment_data = object["attachment"] || []
attachment_data = attachment_data ++ if object["type"] == "Video", do: [object], else: []
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
created_at = Utils.to_masto_date(object["published"])
......
......@@ -132,6 +132,7 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
banner: Keyword.get(instance, :banner_upload_limit),
background: Keyword.get(instance, :background_upload_limit)
},
accountActivationRequired: Keyword.get(instance, :account_activation_required, false),
invitesEnabled: Keyword.get(instance, :invites_enabled, false),
features: features
}
......
......@@ -31,6 +31,7 @@ def create_authorization(conn, %{
}) do
with %User{} = user <- User.get_by_nickname_or_email(name),
true <- Pbkdf2.checkpw(password, user.password_hash),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
%App{} = app <- Repo.get_by(App, client_id: client_id),
{:ok, auth} <- Authorization.create_authorization(app, user) do
# Special case: Local MastodonFE.
......@@ -63,6 +64,15 @@ def create_authorization(conn, %{
redirect(conn, external: url)
end
else
{:auth_active, false} ->
conn
|> put_flash(:error, "Account confirmation pending")
|> put_status(:forbidden)
|> authorize(params)
error ->
error
end
end
......@@ -101,6 +111,7 @@ def token_exchange(
with %App{} = app <- get_app_from_request(conn, params),
%User{} = user <- User.get_by_nickname_or_email(name),
true <- Pbkdf2.checkpw(password, user.password_hash),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
{:ok, auth} <- Authorization.create_authorization(app, user),
{:ok, token} <- Token.exchange_token(app, auth) do
response = %{
......@@ -113,6 +124,11 @@ def token_exchange(
json(conn, response)
else
{:auth_active, false} ->
conn
|> put_status(:forbidden)
|> json(%{error: "Account confirmation pending"})
_error ->
put_status(conn, 400)
|> json(%{error: "Invalid credentials"})
......
......@@ -283,6 +283,15 @@ defmodule Pleroma.Web.Router do
post("/account/register", TwitterAPI.Controller, :register)
post("/account/password_reset", TwitterAPI.Controller, :password_reset)
get(
"/account/confirm_email/:user_id/:token",
TwitterAPI.Controller,
:confirm_email,
as: :confirm_email
)
post("/account/resend_confirmation_email", TwitterAPI.Controller, :resend_confirmation_email)
get("/search", TwitterAPI.Controller, :search)
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
end
......
......@@ -174,6 +174,8 @@ def config(conn, _params) do
closed: if(Keyword.get(instance, :registrations_open), do: "0", else: "1"),
private: if(Keyword.get(instance, :public, true), do: "0", else: "1"),
vapidPublicKey: vapid_public_key,
accountActivationRequired:
if(Keyword.get(instance, :account_activation_required, false), do: "1", else: "0"),
invitesEnabled: if(Keyword.get(instance, :invites_enabled, false), do: "1", else: "0")
}
......
......@@ -171,14 +171,7 @@ def to_map(
HTML.filter_tags(content, User.html_filter_policy(opts[:for]))
|> Formatter.emojify(object["emoji"])
video =
if object["type"] == "Video" do
[object]
else
[]
end
attachments = (object["attachment"] || []) ++ video
attachments = object["attachment"] || []
reply_parent = Activity.get_in_reply_to_activity(activity)
......
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
alias Pleroma.{UserInviteToken, User, Activity, Repo, Object}
alias Pleroma.{UserEmail, Mailer}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.CommonAPI
import Ecto.Query
def create_status(%User{} = user, %{"status" => _} = data) do
......@@ -161,10 +163,11 @@ def register_user(params) do
cond do
registrations_open || (!is_nil(token) && !token.used) ->
changeset = User.register_changeset(%User{info: %{}}, params)
changeset = User.register_changeset(%User{}, params)
with {:ok, user} <- Repo.insert(changeset) do
with {:ok, user} <- User.register(changeset) do
!registrations_open && UserInviteToken.mark_as_used(token.token)
{:ok, user}
else
{:error, changeset} ->
......@@ -189,8 +192,8 @@ def password_reset(nickname_or_email) do
%User{local: true} = user <- User.get_by_nickname_or_email(nickname_or_email),
{:ok, token_record} <- Pleroma.PasswordResetToken.create_token(user) do
user
|> Pleroma.UserEmail.password_reset_email(token_record.token)
|> Pleroma.Mailer.deliver()
|> UserEmail.password_reset_email(token_record.token)
|> Mailer.deliver()
else
false ->
{:error, "bad user identifier"}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment