diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json
index 8c2f768f0..e34bb1e44 100644
--- a/js/src/i18n/en_US.json
+++ b/js/src/i18n/en_US.json
@@ -1251,5 +1251,6 @@
"Approve member": "Approve member",
"Reject member": "Reject member",
"The membership request from {profile} was rejected": "The membership request from {profile} was rejected",
- "The member was approved": "The member was approved"
+ "The member was approved": "The member was approved",
+ "Emails usually don't contain capitals, make sure you haven't made a typo.": "Emails usually don't contain capitals, make sure you haven't made a typo."
}
diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json
index 3d475c107..72ec50fa8 100644
--- a/js/src/i18n/fr_FR.json
+++ b/js/src/i18n/fr_FR.json
@@ -1355,5 +1355,6 @@
"Approve member": "Approuver le ou la membre",
"Reject member": "Rejeter le ou la membre",
"The membership request from {profile} was rejected": "La demande d'adhésion de {profile} a été rejetée",
- "The member was approved": "Le ou la membre a été approuvée"
+ "The member was approved": "Le ou la membre a été approuvée",
+ "Emails usually don't contain capitals, make sure you haven't made a typo.": "Les emails ne contiennent d'ordinaire pas de capitales, assurez-vous de n'avoir pas fait de faute de frappe."
}
diff --git a/js/src/types/apollo.ts b/js/src/types/apollo.ts
new file mode 100644
index 000000000..244b56005
--- /dev/null
+++ b/js/src/types/apollo.ts
@@ -0,0 +1,5 @@
+import { GraphQLError } from "graphql/error/GraphQLError";
+
+export class AbsintheGraphQLError extends GraphQLError {
+ readonly field: string | undefined;
+}
diff --git a/js/src/views/User/Register.vue b/js/src/views/User/Register.vue
index a1638a75d..bb7710963 100644
--- a/js/src/views/User/Register.vue
+++ b/js/src/views/User/Register.vue
@@ -83,8 +83,8 @@
-
-
- {{
- error
- }}
-
@@ -191,13 +185,18 @@
@@ -302,4 +353,7 @@ p.create-account {
margin: 1rem auto 2rem;
}
}
+::v-deep .help.is-warning {
+ color: #755033;
+}
diff --git a/lib/graphql/resolvers/user.ex b/lib/graphql/resolvers/user.ex
index 430bc6b48..afbd0909f 100644
--- a/lib/graphql/resolvers/user.ex
+++ b/lib/graphql/resolvers/user.ex
@@ -145,13 +145,17 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
"""
@spec create_user(any, %{email: String.t()}, any) :: {:ok, User.t()} | {:error, String.t()}
def create_user(_parent, %{email: email} = args, _resolution) do
- with :registration_ok <- check_registration_config(email),
+ with {:ok, email} <- lowercase_domain(email),
+ :registration_ok <- check_registration_config(email),
:not_deny_listed <- check_registration_denylist(email),
- {:ok, %User{} = user} <- Users.register(args),
+ {:ok, %User{} = user} <- Users.register(%{args | email: email}),
%Bamboo.Email{} <-
Email.User.send_confirmation_email(user, Map.get(args, :locale, "en")) do
{:ok, user}
else
+ {:error, :invalid_email} ->
+ {:error, dgettext("errors", "Your email seems to be using an invalid format")}
+
:registration_closed ->
{:error, dgettext("errors", "Registrations are not open")}
@@ -190,24 +194,40 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
# Remove everything behind the +
email = String.replace(email, ~r/(\+.*)(?=\@)/, "")
- if email_in_list(email, Config.instance_registrations_denylist()),
+ if email_in_list?(email, Config.instance_registrations_denylist()),
do: :deny_listed,
else: :not_deny_listed
end
@spec check_allow_listed_email(String.t()) :: :registration_ok | :not_allowlisted
defp check_allow_listed_email(email) do
- if email_in_list(email, Config.instance_registrations_allowlist()),
+ if email_in_list?(email, Config.instance_registrations_allowlist()),
do: :registration_ok,
else: :not_allowlisted
end
- defp email_in_list(email, list) do
- [_, domain] = String.split(email, "@", parts: 2, trim: true)
+ @spec email_in_list?(String.t(), list(String.t())) :: boolean()
+ defp email_in_list?(email, list) do
+ [_, domain] = split_email(email)
domain in list or email in list
end
+ # Domains should always be lower-case, so let's force that
+ @spec lowercase_domain(String.t()) :: {:ok, String.t()} | {:error, :invalid_email}
+ defp lowercase_domain(email) do
+ case split_email(email) do
+ [user_part, domain_part] ->
+ {:ok, "#{user_part}@#{String.downcase(domain_part)}"}
+
+ _ ->
+ {:error, :invalid_email}
+ end
+ end
+
+ @spec split_email(String.t()) :: list(String.t())
+ defp split_email(email), do: String.split(email, "@", parts: 2, trim: true)
+
@doc """
Validate an user, get its actor and a token
"""
diff --git a/test/graphql/resolvers/user_test.exs b/test/graphql/resolvers/user_test.exs
index 293fcb212..0f77eae21 100644
--- a/test/graphql/resolvers/user_test.exs
+++ b/test/graphql/resolvers/user_test.exs
@@ -454,6 +454,21 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
Config.put([:instance, :registration_email_denylist], [])
end
+ test "create_user/3 lowers domain part of email",
+ %{
+ conn: conn
+ } do
+ res =
+ conn
+ |> AbsintheHelpers.graphql_query(
+ query: @create_user_mutation,
+ variables: Map.put(@user_creation, :email, "test+alias@DEMO.tld")
+ )
+
+ assert res["errors"] == nil
+ assert res["data"]["createUser"]["email"] == "test+alias@demo.tld"
+ end
+
test "register_person/3 doesn't register a profile from an unknown email", %{conn: conn} do
conn
|> put_req_header("accept-language", "fr")