Validate external_links on both server and client sides.

Display errors from SaveAdminSettings to the user.
This commit is contained in:
Massedil
2025-09-12 20:54:35 +02:00
parent 66f3956d14
commit 3a3c452e92
3 changed files with 71 additions and 14 deletions

View File

@@ -5,6 +5,7 @@ defmodule Mobilizon.Admin do
import Ecto.Query
import EctoEnum
import Mobilizon.Web.Gettext, only: [dgettext: 2]
alias Mobilizon.Actors.Actor
alias Mobilizon.{Admin, Users}
@@ -182,10 +183,19 @@ defmodule Mobilizon.Admin do
def save_settings(group, args) do
{medias, values} = Map.split(args, [:instance_logo, :instance_favicon, :default_picture])
multi =
Multi.new()
|> do_save_media_setting(group, medias)
|> do_save_value_setting(group, values)
|> Repo.transaction()
case multi do
{:ok, result} ->
{:ok, result}
{:error, _err, %Ecto.Changeset{} = err, _} ->
{:error, err}
end
end
@spec do_save_value_setting(Ecto.Multi.t(), String.t(), map()) :: Ecto.Multi.t()
@@ -195,15 +205,20 @@ defmodule Mobilizon.Admin do
key = hd(Map.keys(args))
{val, rest} = Map.pop(args, key)
changeset =
%Setting{}
|> Setting.changeset(%{
group: group,
name: Atom.to_string(key),
value: convert_to_string(val)
})
|> maybe_validate_external_links(key, val)
transaction =
Multi.insert(
transaction,
key,
Setting.changeset(%Setting{}, %{
group: group,
name: Atom.to_string(key),
value: convert_to_string(val)
}),
changeset,
on_conflict: :replace_all,
conflict_target: [:group, :name]
)
@@ -251,4 +266,38 @@ defmodule Mobilizon.Admin do
val -> to_string(val)
end
end
defp maybe_validate_external_links(changeset, :external_links, external_links) do
external_links
|> List.wrap()
|> Enum.reduce(changeset, fn link, cs ->
case validate_external_link(link) do
{:ok, _} -> cs
{:error, msg} -> Ecto.Changeset.add_error(cs, :external_links, msg)
end
end)
end
defp maybe_validate_external_links(changeset, _key, _val), do: changeset
defp validate_external_link(%{:url => url, :label => label} = link) do
uri = URI.parse(url)
cond do
is_nil(label) or String.trim(label) == "" ->
{:error, dgettext("errors", "External link label cannot be blank")}
String.length(label) < 2 ->
{:error, dgettext("errors", "External link label must be at least 2 characters")}
String.length(label) > 256 ->
{:error, dgettext("errors", "External link label must be at most 256 characters")}
not (uri.scheme in ["http", "https"] and is_binary(uri.host) and uri.host != "") ->
{:error, dgettext("errors", "External link URL must be a valid http/https URL")}
true ->
{:ok, link}
end
end
end

View File

@@ -1292,7 +1292,7 @@
"This profile is from another instance, the informations shown here may be incomplete.": "Ce profil provient d'une autre instance, les informations montrées ici peuvent être incomplètes.",
"This profile is located on this instance, so you need to {access_the_corresponding_account} to suspend it.": "Ce profil se situe sur cette instance, vous devez donc {access_the_corresponding_account} afin de le suspendre.",
"This profile was not found": "Ce profil n'a pas été trouvé",
"This section lets you add links to external websites to the menu.": "Cette section vous permet d'ajouter des liens vers des sites internets externes au menu.",
"This section lets you add links to external websites to the menu.": "Cette section vous permet d'ajouter des liens vers des sites internet externes au menu.",
"This setting will be used to display the website and send you emails in the correct language.": "Ce paramètre sera utilisé pour l'affichage du site et pour vous envoyer des courriels dans la bonne langue.",
"This URL doesn't seem to be valid": "Cette URL ne semble pas être valide",
"This URL is not supported": "Cette URL n'est pas supportée",

View File

@@ -480,11 +480,17 @@
:key="index"
>
<o-field :label="t('URL')" class="!mt-0"
><o-input expanded v-model="link.url" type="text"
><o-input expanded v-model="link.url" type="url" required
/></o-field>
<o-field :label="t('Label')"
><o-input expanded v-model="link.label" type="text"
><o-input
expanded
v-model="link.label"
type="text"
minlength="2"
maxlength="256"
required
/></o-field>
<o-field
@@ -609,7 +615,7 @@ const addLink = () => {
settingsToWrite.value.externalLinks.push({
url: "",
label: "",
enabled: false,
enabled: true,
});
};
@@ -700,7 +706,9 @@ saveAdminSettingsDone(() => {
saveAdminSettingsError((e) => {
console.error(e);
notifier?.error(t("Failed to save admin settings") as string);
notifier?.error(
(t("Failed to save admin settings") as string) + ": " + e.message
);
});
const updateSettings = async (): Promise<void> => {