Merge branch 'bug/differenciate-user-not-found-and-user-not-activated' into 'master'

Translate them and handle difference between user not found and user not confirmed

Closes #212

See merge request framasoft/mobilizon!279
This commit is contained in:
Thomas Citharel 2019-10-16 17:20:53 +02:00
commit 90a1d3276f
11 changed files with 78 additions and 17 deletions

View file

@ -1,6 +1,7 @@
{ {
"name": "mobilizon", "name": "mobilizon",
"version": "1.0.0-beta.1", "version": "1.0.0-beta.1",
"license": "AGPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "vue-cli-service build", "build": "vue-cli-service build",

View file

@ -125,6 +125,7 @@
"Identity {displayName} updated": "Identity {displayName} updated", "Identity {displayName} updated": "Identity {displayName} updated",
"If an account with this email exists, we just sent another confirmation email to {email}": "If an account with this email exists, we just sent another confirmation email to {email}", "If an account with this email exists, we just sent another confirmation email to {email}": "If an account with this email exists, we just sent another confirmation email to {email}",
"If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.", "If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.",
"Impossible to login, your email or password seems incorrect.": "Impossible to login, your email or password seems incorrect.",
"In the meantime, please consider that the software is not (yet) finished. More information {onBlog}.": "In the meantime, please consider that the software is not (yet) finished. More information {onBlog}.", "In the meantime, please consider that the software is not (yet) finished. More information {onBlog}.": "In the meantime, please consider that the software is not (yet) finished. More information {onBlog}.",
"Installing Mobilizon will allow communities to free themselves from the services of tech giants by creating <b>their own event platform</b>.": "Installing Mobilizon will allow communities to free themselves from the services of tech giants by creating <b>their own event platform</b>.", "Installing Mobilizon will allow communities to free themselves from the services of tech giants by creating <b>their own event platform</b>.": "Installing Mobilizon will allow communities to free themselves from the services of tech giants by creating <b>their own event platform</b>.",
"Join {instance}, a Mobilizon instance": "Join {instance}, a Mobilizon instance", "Join {instance}, a Mobilizon instance": "Join {instance}, a Mobilizon instance",
@ -160,6 +161,7 @@
"No group found": "No group found", "No group found": "No group found",
"No groups found": "No groups found", "No groups found": "No groups found",
"No results for \"{queryText}\"": "No results for \"{queryText}\"", "No results for \"{queryText}\"": "No results for \"{queryText}\"",
"No user account with this email was found. Maybe you made a typo?": "No user account with this email was found. Maybe you made a typo?",
"Number of places": "Number of places", "Number of places": "Number of places",
"OK": "OK", "OK": "OK",
"Old password": "Old password", "Old password": "Old password",
@ -251,6 +253,7 @@
"The page you're looking for doesn't exist.": "The page you're looking for doesn't exist.", "The page you're looking for doesn't exist.": "The page you're looking for doesn't exist.",
"The password was successfully changed": "The password was successfully changed", "The password was successfully changed": "The password was successfully changed",
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "The report will be sent to the moderators of your instance. You can explain why you report this content below.", "The report will be sent to the moderators of your instance. You can explain why you report this content below.": "The report will be sent to the moderators of your instance. You can explain why you report this content below.",
"The user account you're trying to login as has not been confirmed yet. Check your email inbox and eventually your spam folder.": "The user account you're trying to login as has not been confirmed yet. Check your email inbox and eventually your spam folder.",
"There are {participants} participants.": "There are {participants} participants.", "There are {participants} participants.": "There are {participants} participants.",
"These events may interest you": "These events may interest you", "These events may interest you": "These events may interest you",
"This installation (called “instance“) can easily {interconnect}, thanks to {protocol}.": "This installation (called “instance“) can easily {interconnect}, thanks to {protocol}.", "This installation (called “instance“) can easily {interconnect}, thanks to {protocol}.": "This installation (called “instance“) can easily {interconnect}, thanks to {protocol}.",
@ -297,6 +300,7 @@
"You have one event in {days} days.": "You have no events in {days} days | You have one event in {days} days. | You have {count} events in {days} days", "You have one event in {days} days.": "You have no events in {days} days | You have one event in {days} days. | You have {count} events in {days} days",
"You have one event today.": "You have no events today | You have one event today. | You have {count} events today", "You have one event today.": "You have no events today | You have one event today. | You have {count} events today",
"You have one event tomorrow.": "You have no events tomorrow | You have one event tomorrow. | You have {count} events tomorrow", "You have one event tomorrow.": "You have no events tomorrow | You have one event tomorrow. | You have {count} events tomorrow",
"You may also ask to {resend_confirmation_email}.": "You may also ask to {resend_confirmation_email}.",
"You need to login.": "You need to login.", "You need to login.": "You need to login.",
"Your account has been validated": "Your account has been validated", "Your account has been validated": "Your account has been validated",
"Your account is being validated": "Your account is being validated", "Your account is being validated": "Your account is being validated",
@ -311,6 +315,7 @@
"interconnect with others like it": "interconnect with others like it", "interconnect with others like it": "interconnect with others like it",
"its source code is public": "its source code is public", "its source code is public": "its source code is public",
"on our blog": "on our blog", "on our blog": "on our blog",
"resend confirmation email": "resend confirmation email",
"respect of the fundamental freedoms": "respect of the fundamental freedoms", "respect of the fundamental freedoms": "respect of the fundamental freedoms",
"with another identity…": "with another identity…", "with another identity…": "with another identity…",
"with {identity}": "with {identity}", "with {identity}": "with {identity}",

View file

@ -125,6 +125,7 @@
"Identity {displayName} updated": "Identité {displayName} mise à jour", "Identity {displayName} updated": "Identité {displayName} mise à jour",
"If an account with this email exists, we just sent another confirmation email to {email}": "Si un compte avec un tel email existe, nous venons juste d'envoyer un nouvel email de confirmation à {email}", "If an account with this email exists, we just sent another confirmation email to {email}": "Si un compte avec un tel email existe, nous venons juste d'envoyer un nouvel email de confirmation à {email}",
"If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "Si cette identité est la seule administratrice de certains groupes, vous devez les supprimer avant de pouvoir supprimer cette identité.", "If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "Si cette identité est la seule administratrice de certains groupes, vous devez les supprimer avant de pouvoir supprimer cette identité.",
"Impossible to login, your email or password seems incorrect.": "Impossible de se connecter, votre email ou bien votre mot de passe semble incorrect.",
"In the meantime, please consider that the software is not (yet) finished. More information {onBlog}.": "D'ici là, veuillez considérer que le logiciel n'est pas (encore) fini. Plus d'informations {onBlog}.", "In the meantime, please consider that the software is not (yet) finished. More information {onBlog}.": "D'ici là, veuillez considérer que le logiciel n'est pas (encore) fini. Plus d'informations {onBlog}.",
"Installing Mobilizon will allow communities to free themselves from the services of tech giants by creating <b>their own event platform</b>.": "Installer Mobilizon permettra à des collectifs de sémanciper des outils des géants du web en créant <b>leur propre plateforme dévénements</b>.", "Installing Mobilizon will allow communities to free themselves from the services of tech giants by creating <b>their own event platform</b>.": "Installer Mobilizon permettra à des collectifs de sémanciper des outils des géants du web en créant <b>leur propre plateforme dévénements</b>.",
"Join {instance}, a Mobilizon instance": "Rejoignez {instance}, une instance Mobilizon", "Join {instance}, a Mobilizon instance": "Rejoignez {instance}, une instance Mobilizon",
@ -160,6 +161,7 @@
"No group found": "Aucun groupe trouvé", "No group found": "Aucun groupe trouvé",
"No groups found": "Aucun groupe trouvé", "No groups found": "Aucun groupe trouvé",
"No results for \"{queryText}\"": "Pas de résultats pour « {queryText} »", "No results for \"{queryText}\"": "Pas de résultats pour « {queryText} »",
"No user account with this email was found. Maybe you made a typo?": "Aucun compte utilisateur trouvé pour cet email. Peut-être avez-vous fait une faute de frappe ?",
"Number of places": "Nombre de places", "Number of places": "Nombre de places",
"OK": "OK", "OK": "OK",
"Old password": "Ancien mot de passe", "Old password": "Ancien mot de passe",
@ -251,6 +253,7 @@
"The page you're looking for doesn't exist.": "La page que vous recherchez n'existe pas.", "The page you're looking for doesn't exist.": "La page que vous recherchez n'existe pas.",
"The password was successfully changed": "Le mot de passe a été changé avec succès", "The password was successfully changed": "Le mot de passe a été changé avec succès",
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "Le signalement sera envoyé aux modérateur⋅ices de votre instance. Vous pouvez expliquer pourquoi vous signalez ce contenu ci-dessous.", "The report will be sent to the moderators of your instance. You can explain why you report this content below.": "Le signalement sera envoyé aux modérateur⋅ices de votre instance. Vous pouvez expliquer pourquoi vous signalez ce contenu ci-dessous.",
"The user account you're trying to login as has not been confirmed yet. Check your email inbox and eventually your spam folder.": "Le compte utilisateur avec lequel vous essayez de vous connectez n'a pas été confirmé. Vérifiez la boite de réception de votre adresse email et éventuellement le dossier des messages indésirables.",
"There are {participants} participants.": "Il n'y a qu'un⋅e participant⋅e. | Il y a {participants} participants.", "There are {participants} participants.": "Il n'y a qu'un⋅e participant⋅e. | Il y a {participants} participants.",
"These events may interest you": "Ces événements peuvent vous intéresser", "These events may interest you": "Ces événements peuvent vous intéresser",
"This installation (called “instance“) can easily {interconnect}, thanks to {protocol}.": "Cette installation (appelée “instance“) peut facilement {interconnect}, grâce à {protocol}.", "This installation (called “instance“) can easily {interconnect}, thanks to {protocol}.": "Cette installation (appelée “instance“) peut facilement {interconnect}, grâce à {protocol}.",
@ -297,6 +300,7 @@
"You have one event in {days} days.": "Vous n'avez pas d'événements dans {days} jours | Vous avez un événement dans {days} jours. | Vous avez {count} événements dans {days} jours", "You have one event in {days} days.": "Vous n'avez pas d'événements dans {days} jours | Vous avez un événement dans {days} jours. | Vous avez {count} événements dans {days} jours",
"You have one event today.": "Vous n'avez pas d'évenement aujourd'hui | Vous avez un événement aujourd'hui. | Vous avez {count} événements aujourd'hui", "You have one event today.": "Vous n'avez pas d'évenement aujourd'hui | Vous avez un événement aujourd'hui. | Vous avez {count} événements aujourd'hui",
"You have one event tomorrow.": "Vous n'avez pas d'événement demain | Vous avez un événement demain. | Vous avez {count} événements demain", "You have one event tomorrow.": "Vous n'avez pas d'événement demain | Vous avez un événement demain. | Vous avez {count} événements demain",
"You may also ask to {resend_confirmation_email}.": "Vous pouvez aussi demander à {resend_confirmation_email}.",
"You need to login.": "Vous devez vous connecter.", "You need to login.": "Vous devez vous connecter.",
"Your account has been validated": "Votre compte a été validé", "Your account has been validated": "Votre compte a été validé",
"Your account is being validated": "Votre compte est en cours de validation", "Your account is being validated": "Votre compte est en cours de validation",
@ -311,6 +315,7 @@
"interconnect with others like it": "sinterconnecter simplement avec dautres", "interconnect with others like it": "sinterconnecter simplement avec dautres",
"its source code is public": "son code source est public", "its source code is public": "son code source est public",
"on our blog": "sur notre blog", "on our blog": "sur notre blog",
"resend confirmation email": "réenvoyer l'email de confirmation",
"respect of the fundamental freedoms": "le respect des libertés fondamentales", "respect of the fundamental freedoms": "le respect des libertés fondamentales",
"with another identity…": "avec une autre identité…", "with another identity…": "avec une autre identité…",
"with {identity}": "avec {identity}", "with {identity}": "avec {identity}",

View file

@ -117,7 +117,7 @@
"About this event": "Tocant aqueste eveniment", "About this event": "Tocant aqueste eveniment",
"© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Loscontribuidors de Mobilizon {date} - Fach amb Elixir, Phoenix, VueJS e damor e de setmanas", "© The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "© Loscontribuidors de Mobilizon {date} - Fach amb Elixir, Phoenix, VueJS e damor e de setmanas",
"{count} requests waiting": "Una demanda en espèra|{count} demandas en espèra", "{count} requests waiting": "Una demanda en espèra|{count} demandas en espèra",
"{count} participants": "Aucun participant|Un participant|{count} participants", "{count} participants": "Cap de participacion pel moment|Un participant|{count} participants",
"{approved} / {total} seats": "{approved} / {total} plaças", "{approved} / {total} seats": "{approved} / {total} plaças",
"You need to login.": "Vos cal vos connectar.", "You need to login.": "Vos cal vos connectar.",
"You are an organizer.": "Sètz un organizaire.", "You are an organizer.": "Sètz un organizaire.",

View file

@ -1,3 +1,9 @@
export enum LoginErrorCode { export enum LoginErrorCode {
NEED_TO_LOGIN = 'rouge', NEED_TO_LOGIN = 'rouge',
} }
export enum LoginError {
USER_NOT_CONFIRMED = 'User account not confirmed',
USER_DOES_NOT_EXIST = 'No user with this email was found',
USER_EMAIL_PASSWORD_INVALID = 'Impossible to authenticate, either your email or password are invalid.',
}

View file

@ -49,17 +49,13 @@ export const errors: IError[] = [
suggestRefresh: false, suggestRefresh: false,
}, },
{ {
match: /^User with email not found$/, match: /^No user with this email was found$/,
value: null, value: null,
}, },
{ {
match: /^Username is already taken$/, match: /^Username is already taken$/,
value: null, value: null,
}, },
{
match: /^No user with this email was found$/,
value: null,
},
{ {
match: /^Impossible to authenticate, either your email or password are invalid.$/, match: /^Impossible to authenticate, either your email or password are invalid.$/,
value: null, value: null,
@ -72,4 +68,8 @@ export const errors: IError[] = [
match: /^This email is already used.$/, match: /^This email is already used.$/,
value: null, value: null,
}, },
{
match: /^User account not confirmed$/,
value: null,
},
]; ];

View file

@ -3,14 +3,27 @@
<b-message v-if="errorCode === LoginErrorCode.NEED_TO_LOGIN" title="Info" type="is-info"> <b-message v-if="errorCode === LoginErrorCode.NEED_TO_LOGIN" title="Info" type="is-info">
{{ $t('You need to login.') }} {{ $t('You need to login.') }}
</b-message> </b-message>
<section v-if="!currentUser.isLoggedIn"> <section v-if="!currentUser.isLoggedIn">
<div class="columns is-mobile is-centered"> <div class="columns is-mobile is-centered">
<div class="column is-half-desktop"> <div class="column is-half-desktop">
<h1 class="title"> <h1 class="title">
{{ $t('Welcome back!') }} {{ $t('Welcome back!') }}
</h1> </h1>
<b-message title="Error" type="is-danger" v-for="error in errors" :key="error">{{ error }}</b-message> <b-message title="Error" type="is-danger" v-for="error in errors" :key="error">
<span v-if="error === LoginError.USER_NOT_CONFIRMED">
<span>{{ $t("The user account you're trying to login as has not been confirmed yet. Check your email inbox and eventually your spam folder.") }}</span>
<i18n path="You may also ask to {resend_confirmation_email}.">
<router-link slot="resend_confirmation_email" :to="{ name: RouteName.RESEND_CONFIRMATION }">{{ $t('resend confirmation email') }}</router-link>
</i18n>
</span>
<span v-if="error === LoginError.USER_EMAIL_PASSWORD_INVALID">
{{ $t('Impossible to login, your email or password seems incorrect.') }}
</span>
<!-- TODO: Shouldn't we hide this information ? -->
<span v-if="error === LoginError.USER_DOES_NOT_EXIST">
{{ $t('No user account with this email was found. Maybe you made a typo?') }}
</span>
</b-message>
<form @submit="loginAction"> <form @submit="loginAction">
<b-field :label="$t('Email')"> <b-field :label="$t('Email')">
<b-input aria-required="true" required type="email" v-model="credentials.email"/> <b-input aria-required="true" required type="email" v-model="credentials.email"/>
@ -67,7 +80,7 @@ import { ILogin } from '@/types/login.model';
import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user'; import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user';
import { onLogin } from '@/vue-apollo'; import { onLogin } from '@/vue-apollo';
import { RouteName } from '@/router'; import { RouteName } from '@/router';
import { LoginErrorCode } from '@/types/login-error-code.model'; import { LoginErrorCode, LoginError } from '@/types/login-error-code.model';
import { ICurrentUser } from '@/types/current-user.model'; import { ICurrentUser } from '@/types/current-user.model';
import { CONFIG } from '@/graphql/config'; import { CONFIG } from '@/graphql/config';
import { IConfig } from '@/types/config.model'; import { IConfig } from '@/types/config.model';
@ -95,6 +108,7 @@ export default class Login extends Vue {
@Prop({ type: String, required: false, default: '' }) password!: string; @Prop({ type: String, required: false, default: '' }) password!: string;
LoginErrorCode = LoginErrorCode; LoginErrorCode = LoginErrorCode;
LoginError = LoginError;
errorCode: LoginErrorCode | null = null; errorCode: LoginErrorCode | null = null;
config!: IConfig; config!: IConfig;
@ -106,7 +120,6 @@ export default class Login extends Vue {
email: '', email: '',
password: '', password: '',
}; };
validationSent = false;
errors: string[] = []; errors: string[] = [];
rules = { rules = {

View file

@ -37,7 +37,7 @@ describe('Login', () => {
cy.get('input[type=password]').type('badPassword').should('have.value', 'badPassword'); cy.get('input[type=password]').type('badPassword').should('have.value', 'badPassword');
cy.contains('button.button.is-primary.is-large', 'Login').click(); cy.contains('button.button.is-primary.is-large', 'Login').click();
cy.contains('.message.is-danger', 'User with email not found'); cy.contains('.message.is-danger', 'No user account with this email was found. Maybe you made a typo?');
}); });
it('Tries to login with valid credentials', () => { it('Tries to login with valid credentials', () => {
@ -56,7 +56,7 @@ describe('Login', () => {
cy.get('input[type=email]').type('unconfirmed@email.com'); cy.get('input[type=email]').type('unconfirmed@email.com');
cy.get('input[type=password]').type('some password'); cy.get('input[type=password]').type('some password');
cy.get('form').submit(); cy.get('form').submit();
cy.contains('.message.is-danger', 'User with email not found'); cy.contains('.message.is-danger', 'The user account you\'re trying to login as has not been confirmed yet. Check your email inbox and eventually your spam folder.You may also ask to resend confirmation email.');
}); });
it('Tries to login with valid credentials, confirmed account but no profile', () => { it('Tries to login with valid credentials, confirmed account but no profile', () => {

View file

@ -177,7 +177,7 @@ defmodule MobilizonWeb.Resolvers.Person do
{:ok, new_person} {:ok, new_person}
else else
{:error, :user_not_found} -> {:error, :user_not_found} ->
{:error, "User with email not found"} {:error, "No user with this email was found"}
{:no_actor, _} -> {:no_actor, _} ->
{:error, "You already have a profile for this user"} {:error, "You already have a profile for this user"}

View file

@ -67,13 +67,16 @@ defmodule MobilizonWeb.Resolvers.User do
Login an user. Returns a token and the user Login an user. Returns a token and the user
""" """
def login_user(_parent, %{email: email, password: password}, _resolution) do def login_user(_parent, %{email: email, password: password}, _resolution) do
with {:ok, %User{} = user} <- Users.get_user_by_email(email, true), with {:ok, %User{confirmed_at: %DateTime{}} = user} <- Users.get_user_by_email(email),
{:ok, %{access_token: access_token, refresh_token: refresh_token}} <- {:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
Users.authenticate(%{user: user, password: password}) do Users.authenticate(%{user: user, password: password}) do
{:ok, %{access_token: access_token, refresh_token: refresh_token, user: user}} {:ok, %{access_token: access_token, refresh_token: refresh_token, user: user}}
else else
{:ok, %User{confirmed_at: nil} = _user} ->
{:error, "User account not confirmed"}
{:error, :user_not_found} -> {:error, :user_not_found} ->
{:error, "User with email not found"} {:error, "No user with this email was found"}
{:error, :unauthorized} -> {:error, :unauthorized} ->
{:error, "Impossible to authenticate, either your email or password are invalid."} {:error, "Impossible to authenticate, either your email or password are invalid."}

View file

@ -359,7 +359,8 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
context.conn context.conn
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert hd(json_response(res, 200)["errors"])["message"] == "User with email not found" assert hd(json_response(res, 200)["errors"])["message"] ==
"No user with this email was found"
end end
test "register_person/3 can't be called with an existing profile", context do test "register_person/3 can't be called with an existing profile", context do
@ -780,7 +781,34 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do
context.conn context.conn
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert hd(json_response(res, 200)["errors"])["message"] == "User with email not found" assert hd(json_response(res, 200)["errors"])["message"] ==
"No user with this email was found"
end
test "test login_user/3 with unconfirmed user", context do
{:ok, %User{} = user} = Users.register(%{email: "toto@tata.tld", password: "p4ssw0rd"})
mutation = """
mutation {
login(
email: "#{user.email}",
password: "#{user.password}",
) {
accessToken,
user {
default_actor {
preferred_username,
}
}
}
}
"""
res =
context.conn
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
assert hd(json_response(res, 200)["errors"])["message"] == "User account not confirmed"
end end
end end