Allow to edit account email and delete account

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
master
Thomas Citharel 3 years ago
parent 9fc3c7017f
commit 9f007da286
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773

@ -18,6 +18,8 @@ Also make sure to remove the `EnvironmentFile=` line from the systemd service an
### Added
- Possibility to participate anonymously to an event
- Possibility to participate to a remote event (being redirected by providing federated identity)
- Possibility to change email address for the account
- Possibility to delete your account
### Fixed
- Fixed URL search

@ -66,7 +66,8 @@ export default class ParticipationWithAccount extends Vue {
}
private async webFingerFetch(hostname: string, identity: string): Promise<string> {
const data = await ((await fetch(`http://${hostname}/.well-known/webfinger?resource=acct:${identity}`)).json());
const scheme = process.env.NODE_ENV === 'production' ? 'https' : 'http';
const data = await ((await fetch(`${scheme}://${hostname}/.well-known/webfinger?resource=acct:${identity}`)).json());
if (data && Array.isArray(data.links)) {
const link: { template: string } = data.links.find((link: any) => {
return link && typeof link.template === 'string' && link.rel === 'http://ostatus.org/schema/1.0/subscribe';

@ -30,6 +30,15 @@ mutation ValidateUser($token: String!) {
}
`;
export const LOGGED_USER = gql`
query {
loggedUser {
id,
email
}
}
`;
export const CHANGE_PASSWORD = gql`
mutation ChangePassword($oldPassword: String!, $newPassword: String!) {
changePassword(oldPassword: $oldPassword, newPassword: $newPassword) {
@ -38,6 +47,32 @@ export const CHANGE_PASSWORD = gql`
}
`;
export const CHANGE_EMAIL = gql`
mutation ChangeEmail($email: String!, $password: String!) {
changeEmail(email: $email, password: $password) {
id
}
}
`;
export const VALIDATE_EMAIL = gql`
mutation ValidateEmail($token: String!) {
validateEmail(
token: $token
) {
id
}
}
`;
export const DELETE_ACCOUNT = gql`
mutation DeleteAccount($password: String!) {
deleteAccount (password: $password) {
id
}
}
`;
export const CURRENT_USER_CLIENT = gql`
query {
currentUser @client {

@ -8,6 +8,7 @@
"About this instance": "About this instance",
"About": "About",
"Accepted": "Accepted",
"Account settings": "Account settings",
"Add a note": "Add a note",
"Add an address": "Add an address",
"Add an instance": "Add an instance",
@ -26,6 +27,7 @@
"Anonymous participants will be asked to confirm their participation through e-mail.": "Anonymous participants will be asked to confirm their participation through e-mail.",
"Anonymous participations": "Anonymous participations",
"Approve": "Approve",
"Are you really sure you want to delete your whole account? You'll lose everything. Identities, settings, events created, messages and participations will be gone forever.": "Are you really sure you want to delete your whole account? You'll lose everything. Identities, settings, events created, messages and participations will be gone forever.",
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "Are you sure you want to <b>delete</b> this comment? This action cannot be undone.",
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the conversation with the event creator or edit its event instead.": "Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the conversation with the event creator or edit its event instead.",
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "Are you sure you want to cancel the event creation? You'll lose all modifications.",
@ -44,9 +46,9 @@
"Cancel": "Cancel",
"Cancelled: Won't happen": "Cancelled: Won't happen",
"Category": "Category",
"Change my email": "Change my email",
"Change my identityโ€ฆ": "Change my identityโ€ฆ",
"Change my password": "Change my password",
"Change password": "Change password",
"Change": "Change",
"Clear": "Clear",
"Click to select": "Click to select",
@ -86,7 +88,10 @@
"Default": "Default",
"Delete Comment": "Delete Comment",
"Delete Event": "Delete Event",
"Delete account": "Delete account",
"Delete event": "Delete event",
"Delete everything": "Delete everything",
"Delete my account": "Delete my account",
"Delete this identity": "Delete this identity",
"Delete your identity": "Delete your identity",
"Delete {eventTitle}": "Delete {eventTitle}",
@ -94,6 +99,8 @@
"Delete": "Delete",
"Deleting comment": "Deleting comment",
"Deleting event": "Deleting event",
"Deleting my account will delete all of my identities.": "Deleting my account will delete all of my identities.",
"Deleting your Mobilizon account": "Deleting your Mobilizon account",
"Description": "Description",
"Didn't receive the instructions ?": "Didn't receive the instructions ?",
"Display name": "Display name",
@ -105,12 +112,14 @@
"Eg: Stockholm, Dance, Chessโ€ฆ": "Eg: Stockholm, Dance, Chessโ€ฆ",
"Either on the {instance} instance or on another instance.": "Either on the {instance} instance or on another instance.",
"Either the account is already validated, either the validation token is incorrect.": "Either the account is already validated, either the validation token is incorrect.",
"Either the email has already been changed, either the validation token is incorrect.": "Either the email has already been changed, either the validation token is incorrect.",
"Either the participation has already been validated, either the validation token is incorrect.": "Either the participation has already been validated, either the validation token is incorrect.",
"Email": "Email",
"Ends onโ€ฆ": "Ends onโ€ฆ",
"Enjoy discovering Mobilizon!": "Enjoy discovering Mobilizon!",
"Enter the link URL": "Enter the link URL",
"Enter your own terms. HTML tags allowed. Mobilizon.org's terms are provided as template.": "Enter your own terms. HTML tags allowed. Mobilizon.org's terms are provided as template.",
"Error while changing email": "Error while changing email",
"Error while communicating with the server.": "Error while communicating with the server.",
"Error while saving report.": "Error while saving report.",
"Error while validating account": "Error while validating account",
@ -205,6 +214,7 @@
"My events": "My events",
"My identities": "My identities",
"Name": "Name",
"New email": "New email",
"New note": "New note",
"New password": "New password",
"No actors found": "No actors found",
@ -253,7 +263,6 @@
"Participation approval": "Participation approval",
"Participation requested!": "Participation requested!",
"Password (confirmation)": "Password (confirmation)",
"Password change": "Password change",
"Password reset": "Password reset",
"Password": "Password",
"Past events": "Passed events",
@ -261,6 +270,7 @@
"Pick an identity": "Pick an identity",
"Please check your spam folder if you didn't receive the email.": "Please check your spam folder if you didn't receive the email.",
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Please contact this instance's Mobilizon admin if you think this is a mistake.",
"Please enter your password to confirm this action.": "Please enter your password to confirm this action.",
"Please make sure the address is correct and that the page hasn't been moved.": "Please make sure the address is correct and that the page hasn't been moved.",
"Please read the full rules": "Please read the full rules",
"Please refresh the page and retry.": "Please refresh the page and retry.",
@ -331,9 +341,11 @@
"Street": "Street",
"Tentative: Will be confirmed later": "Tentative: Will be confirmed later",
"Terms": "Terms",
"The account's email address was changed. Check your emails to verify it.": "The account's email address was changed. Check your emails to verify it.",
"The actual number of participants may differ, as this event is hosted on another instance.": "The actual number of participants may differ, as this event is hosted on another instance.",
"The content came from another server. Transfer an anonymous copy of the report?": "The content came from another server. Transfer an anonymous copy of the report ?",
"The current identity doesn't have any permission on this event. You should probably change it.": "The current identity doesn't have any permission on this event. You should probably change it.",
"The current password is invalid": "The current password is invalid",
"The draft event has been updated": "The draft event has been updated",
"The event has been created as a draft": "The event has been created as a draft",
"The event has been published": "The event has been published",
@ -341,12 +353,17 @@
"The event has been updated": "The event has been updated",
"The event organizer didn't add any description.": "The event organizer didn't add any description.",
"The event title will be ellipsed.": "The event title will be ellipsed.",
"The new email doesn't seem to be valid": "The new email doesn't seem to be valid",
"The new email must be different": "The new email must be different",
"The new password must be different": "The new password must be different",
"The page you're looking for doesn't exist.": "The page you're looking for doesn't exist.",
"The password provided is invalid": "The password provided is invalid",
"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 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.",
"The {default_terms} will be used. They will be translated in the user's language.": "The {default_terms} will be used. They will be translated in the user's language.",
"There are {participants} participants.": "There are {participants} participants.",
"There will be no way to recover your data.": "There will be no way to recover your data.",
"These events may interest you": "These events may interest you",
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.",
"This email is already registered as participant for this event": "This email is already registered as participant for this event",
@ -412,9 +429,14 @@
"You need to login.": "You need to login.",
"You will be redirected to the original instance": "You will be redirected to the original instance",
"You wish to participate to the following event": "You wish to participate to the following event",
"You'll receive a confirmation email.": "You'll receive a confirmation email.",
"Your account has been successfully deleted": "Your account has been successfully deleted",
"Your account has been validated": "Your account has been validated",
"Your account is being validated": "Your account is being validated",
"Your account is nearly ready, {username}": "Your account is nearly ready, {username}",
"Your current email is {email}. You use it to log in.": "Your current email is {email}. You use it to log in.",
"Your email has been changed": "Your email has been changed",
"Your email is being changed": "Your email is being changed",
"Your email is not whitelisted, you can't register.": "Your email is not whitelisted, you can't register.",
"Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer.": "Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer.",
"Your federated identity": "Your federated identity",
@ -446,4 +468,4 @@
"{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.",
"ยฉ The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "ยฉ The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks",
"ยฉ The OpenStreetMap Contributors": "ยฉ The OpenStreetMap Contributors"
}
}

@ -3,20 +3,21 @@
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Un outil convivial, รฉmancipateur et รฉthique pour se rassembler, s'organiser et se mobiliser.",
"A validation email was sent to {email}": "Un email de validation a รฉtรฉ envoyรฉ ร  {email}",
"Abandon edition": "Abandonner la modification",
"About": "ร€ propos",
"About Mobilizon": "ร€ propos de Mobilizon",
"About this event": "ร€ propos de cet รฉvรจnement",
"About this instance": "ร€ propos de cette instance",
"About": "ร€ propos",
"Accepted": "Acceptรฉ",
"Add": "Ajouter",
"Account settings": "Paramรจtres du compte",
"Add a note": "Ajouter une note",
"Add an address": "Ajouter une adresse",
"Add an instance": "Ajouter une instance",
"Add some tags": "Ajouter des tags",
"Add to my calendar": "Ajouter ร  mon agenda",
"Add": "Ajouter",
"Additional comments": "Commentaires additionnels",
"Admin settings": "Paramรจtres administrateur",
"Admin settings successfully saved.": "Les paramรจtres administrateur ont bien รฉtรฉ sauvegardรฉs",
"Admin settings": "Paramรจtres administrateur",
"Administration": "Administration",
"All the places have already been taken": "Toutes les places ont รฉtรฉ prises|Une place est encore disponible|{places} places sont encore disponibles",
"Allow all comments": "Autoriser tous les commentaires",
@ -26,6 +27,7 @@
"Anonymous participants will be asked to confirm their participation through e-mail.": "Les participants anonymes devront confirmer leur participation par email.",
"Anonymous participations": "Participations anonymes",
"Approve": "Approuver",
"Are you really sure you want to delete your whole account? You'll lose everything. Identities, settings, events created, messages and participations will be gone forever.": "รŠtes-vous vraiment certainโ‹…e de vouloir supprimer votre compte ? Vous allez tout perdre. Identitรฉs, paramรจtres, รฉvรฉnements crรฉรฉs, messages et participations disparaรฎtront pour toujours.",
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "รŠtes-vous certainโ‹…e de vouloir <b>supprimer</b> ce commentaire ? Cette action ne peut pas รชtre annulรฉe.",
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the conversation with the event creator or edit its event instead.": "รŠtes-vous certainโ‹…e de vouloir <b>supprimer</b> cet รฉvรจnement ? Cette action n'est pas rรฉversible. Vous voulez peut-รชtre engager la conversation avec le crรฉateur de l'รฉvรจnement ou bien modifier son รฉvรจnement ร  la place.",
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "ร‰tes-vous certainโ‹…e de vouloir annuler la crรฉation de l'รฉvรจnement ? Vous allez perdre toutes vos modifications.",
@ -36,34 +38,34 @@
"Back to previous page": "Retour ร  la page prรฉcรฉdente",
"Before you can login, you need to click on the link inside it to validate your account": "Avant que vous puissiez vous enregistrer, vous devez cliquer sur le lien ร  l'intรฉrieur pour valider votre compte",
"By {name}": "Par {name}",
"Cancel": "Annuler",
"Cancel anonymous participation": "Annuler ma participation anonyme",
"Cancel creation": "Annuler la crรฉation",
"Cancel edition": "Annuler la modification",
"Cancel my participation requestโ€ฆ": "Annuler ma demande de participationโ€ฆ",
"Cancel my participationโ€ฆ": "Annuler ma participationโ€ฆ",
"Cancel": "Annuler",
"Cancelled: Won't happen": "Annulรฉ : N'aura pas lieu",
"Category": "Catรฉgorie",
"Change": "Modifier",
"Change my email": "Changer mon adresse e-mail",
"Change my identityโ€ฆ": "Changer mon identitรฉโ€ฆ",
"Change my password": "Modifier mon mot de passe",
"Change password": "Modifier mot de passe",
"Change": "Modifier",
"Clear": "Effacer",
"Click to select": "Cliquez pour sรฉlectionner",
"Click to upload": "Cliquez pour uploader",
"Close": "Fermรฉ",
"Close comments for all (except for admins)": "Fermer les commentaires ร  tout le monde (exceptรฉ les administrateurs)",
"Close": "Fermรฉ",
"Closed": "Fermรฉ",
"Comment deleted": "Commentaire supprimรฉ",
"Comment from @{username} reported": "Commentaire de @{username} signalรฉ",
"Comments": "Commentaires",
"Comments have been closed.": "Les commentaires sont fermรฉs.",
"Comments on the event page": "Commentaires sur la page de l'รฉvรฉnement",
"Comments": "Commentaires",
"Confirm my particpation": "Confirmer ma participation",
"Confirmed: Will happen": "Confirmรฉ : aura lieu",
"Continue editing": "Continuer la modification",
"Country": "Pays",
"Create": "Crรฉer",
"Create a new event": "Crรฉer un nouvel รฉvรจnement",
"Create a new group": "Crรฉer un nouveau groupe",
"Create a new identity": "Crรฉer une nouvelle identitรฉ",
@ -74,27 +76,33 @@
"Create my profile": "Crรฉer mon profil",
"Create token": "Crรฉer un jeton",
"Create, edit or delete events": "Crรฉer, modifier ou supprimer des รฉvรจnements",
"Create": "Crรฉer",
"Creator": "Crรฉateur",
"Current identity has been changed to {identityName} in order to manage this event.": "L'identitรฉ actuelle a รฉtรฉ changรฉe ร  {identityName} pour pouvoir gรฉrer cet รฉvรจnement.",
"Custom": "Custom",
"Custom URL": "URL personnalisรฉe",
"Custom text": "Texte personnalisรฉ",
"Custom": "Custom",
"Dashboard": "Tableau de bord",
"Date": "Date",
"Date and time settings": "Paramรจtres de date et d'heure",
"Date parameters": "Paramรจtres de date",
"Default": "Default",
"Date": "Date",
"Default Mobilizon.org terms": "Conditions d'utilisation par dรฉfaut de Mobilizon.org",
"Delete": "Supprimer",
"Default": "Default",
"Delete Comment": "Supprimer le commentaire",
"Delete Event": "Supprimer l'รฉvรจnement",
"Delete account": "Suppression du compte",
"Delete event": "Supprimer un รฉvรจnement",
"Delete everything": "Tout supprimer",
"Delete my account": "Supprimer mon compte",
"Delete this identity": "Supprimer cette identitรฉ",
"Delete your identity": "Supprimer votre identitรฉ",
"Delete {eventTitle}": "Supprimer {eventTitle}",
"Delete {preferredUsername}": "Supprimer {preferredUsername}",
"Delete": "Supprimer",
"Deleting comment": "Suppression du commentaire en cours",
"Deleting event": "Suppression de l'รฉvรจnement",
"Deleting my account will delete all of my identities.": "Supprimer mon compte supprimera toutes mes identitรฉs.",
"Deleting your Mobilizon account": "Supprimer votre compte Mobilizon",
"Description": "Description",
"Didn't receive the instructions ?": "Vous n'avez pas reรงu les instructions ?",
"Display name": "Nom affichรฉ",
@ -106,17 +114,18 @@
"Eg: Stockholm, Dance, Chessโ€ฆ": "Par exemple : Lyon, Danse, Bridgeโ€ฆ",
"Either on the {instance} instance or on another instance.": "Sur l'instance {instance} ou bien sur une autre instance.",
"Either the account is already validated, either the validation token is incorrect.": "Soit le compte est dรฉjร  validรฉ, soit le jeton de validation est incorrect.",
"Either the email has already been changed, either the validation token is incorrect.": "Soit l'adresse email a dรฉjร  รฉtรฉ modifiรฉe, soit le jeton de validation est incorrect.",
"Either the participation has already been validated, either the validation token is incorrect.": "Either the participation has already been validated, either the validation token is incorrect.",
"Email": "Email",
"Ends onโ€ฆ": "Se termine leโ€ฆ",
"Enjoy discovering Mobilizon!": "Amusez-vous bien en dรฉcouvrant Mobilizon !",
"Enter the link URL": "Entrez l'URL du lien",
"Enter your own terms. HTML tags allowed. Mobilizon.org's terms are provided as template.": "Entrez vos propres conditions d'utilisations. Les balises HTML sont autorisรฉes. Les conditions d'utilisation par dรฉfaut de Mobilizon.org sont fournies comme modรจle.",
"Error while changing email": "Erreur lors de la modification de l'adresse email",
"Error while communicating with the server.": "Erreur de communication avec le serveur.",
"Error while saving report.": "Erreur lors de l'enregistrement du signalement.",
"Error while validating account": "Erreur lors de la validation du compte",
"Error while validating participation": "Error lors de la validation de la participation",
"Event": "ร‰vรฉnement",
"Event already passed": "ร‰vรฉnement dรฉjร  passรฉ",
"Event cancelled": "ร‰vรฉnement annulรฉ",
"Event creation": "Crรฉation d'รฉvรจnement",
@ -127,6 +136,7 @@
"Event to be confirmed": "ร‰vรฉnement ร  confirmer",
"Event {eventTitle} deleted": "ร‰vรฉnement {eventTitle} supprimรฉ",
"Event {eventTitle} reported": "ร‰vรฉnement {eventTitle} signalรฉ",
"Event": "ร‰vรฉnement",
"Events": "ร‰vรฉnements",
"Ex: test.mobilizon.org": "Ex : test.mobilizon.org",
"Exclude": "Exclure",
@ -141,8 +151,8 @@
"For instance: London, Taekwondo, Architectureโ€ฆ": "Par exemple : Lyon, Taekwondo, Architectureโ€ฆ",
"Forgot your password ?": "Mot de passe oubliรฉ ?",
"From a birthday party with friends and family to a march for climate change, right now, our gatherings are <b>trapped inside the tech giantsโ€™ platforms</b>. How can we organize, how can we click โ€œAttend,โ€ without <b>providing private data</b> to Facebook or <b>locking ourselves up</b> inside MeetUp?": "De lโ€™anniversaire entre amiยทeยทs ร  une marche pour le climat, aujourdโ€™hui, les bonnes raisons de se rassembler sont <b>captรฉes par les gรฉants du web</b>. Comment sโ€™organiser, comment cliquer sur ยซโ€ฏje participeโ€ฏยป sans <b>livrer des donnรฉes intimes</b> ร  Facebook ou<b> sโ€™enfermer</b> dans MeetUpโ€ฏ?",
"From the {startDate} at {startTime} to the {endDate}": "Du {startDate} ร  {startTime} jusqu'au {endDate}",
"From the {startDate} at {startTime} to the {endDate} at {endTime}": "Du {startDate} ร  {startTime} au {endDate} ร  {endTime}",
"From the {startDate} at {startTime} to the {endDate}": "Du {startDate} ร  {startTime} jusqu'au {endDate}",
"From the {startDate} to the {endDate}": "Du {startDate} au {endDate}",
"Gather โ‹… Organize โ‹… Mobilize": "Rassembler โ‹… Organiser โ‹… Mobiliser",
"General information": "Informations gรฉnรฉrales",
@ -173,15 +183,15 @@
"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>.",
"Instance Description": "Description de l'instance ",
"Instance Name": "Nom de l'instance",
"Instance Terms": "Conditions gรฉnรฉrales de l'instance",
"Instance Terms Source": "Source des conditions d'utilisation de l'instance",
"Instance Terms URL": "URL des conditions gรฉnรฉrales de l'instance",
"Instance Terms": "Conditions gรฉnรฉrales de l'instance",
"Instances": "Instances",
"Join {instance}, a Mobilizon instance": "Rejoignez {instance}, une instance Mobilizon",
"Last published event": "Dernier รฉvรจnement publiรฉ",
"Last week": "La semaine derniรจre",
"Learn more": "En apprendre plus",
"Learn more about Mobilizon": "En apprendre plus ร  propos de Mobilizon",
"Learn more": "En apprendre plus",
"Leave event": "Annuler ma participation ร  l'รฉvรจnement",
"Leaving event \"{title}\"": "Annuler ma participation ร  l'รฉvรจnement",
"Let's create a new common": "Crรฉons un nouveau Common",
@ -191,9 +201,9 @@
"Locality": "Commune",
"Log in": "Se connecter",
"Log out": "Se dรฉconnecter",
"Login": "Se connecter",
"Login on Mobilizon!": "Se connecter sur Mobilizon !",
"Login on {instance}": "Se connecter sur {instance}",
"Login": "Se connecter",
"Manage participations": "Gรฉrer les participations",
"Mark as resolved": "Marquer comme rรฉsolu",
"Members": "Membres",
@ -206,6 +216,7 @@
"My events": "Mes รฉvรจnements",
"My identities": "Mes identitรฉs",
"Name": "Nom",
"New email": "Nouvelle adresse e-mail",
"New note": "Nouvelle note",
"New password": "Nouveau mot de passe",
"No actors found": "Aucun acteur trouvรฉ",
@ -229,18 +240,18 @@
"Number of places": "Nombre de places",
"OK": "OK",
"Old password": "Ancien mot de passe",
"On {date}": "Le {date}",
"On {date} ending at {endTime}": "Le {date}, se terminant ร  {endTime}",
"On {date} from {startTime} to {endTime}": "Le {date} de {startTime} ร  {endTime}",
"On {date} starting at {startTime}": "Le {date} ร  partir de {startTime}",
"On {date}": "Le {date}",
"One person is going": "Personne n'y va | Une personne y va | {approved} personnes y vont",
"Only accessible through link and search (private)": "Uniquement accessibles par lien et la recherche (privรฉ)",
"Only alphanumeric characters and underscores are supported.": "Seuls les caractรจres alphanumรฉriques et les tirets bas sont acceptรฉs.",
"Open": "Ouvert",
"Opened reports": "Signalements ouverts",
"Or": "Ou",
"Organized": "Organisรฉs",
"Organized by {name}": "Organisรฉ par {name}",
"Organized": "Organisรฉs",
"Organizer": "Organisateur",
"Other software may also support this.": "D'autres logiciels peuvent รฉgalement supporter cette fonctionnalitรฉ.",
"Otherwise this identity will just be removed from the group administrators.": "Sinon cette identitรฉ sera juste supprimรฉe des administrateurs du groupe.",
@ -249,19 +260,20 @@
"Participant already was rejected.": "Le participant a dรฉjร  รฉtรฉ refusรฉ.",
"Participant has already been approved as participant.": "Le participant a dรฉjร  รฉtรฉ approuvรฉ en tant que participant.",
"Participants": "Participants",
"Participate": "Participer",
"Participate using your email address": "Participer en utilisant votre adresse email",
"Participate": "Participer",
"Participation approval": "Validation des participations",
"Participation requested!": "Participation demandรฉe !",
"Password": "Mot de passe",
"Password (confirmation)": "Mot de passe (confirmation)",
"Password change": "Changement de mot de passe",
"Password reset": "Rรฉinitialisation du mot de passe",
"Password": "Mot de passe",
"Past events": "ร‰vรฉnements passรฉs",
"Pending": "En attente",
"Pick an identity": "Choisissez une identitรฉ",
"Please check your spam folder if you didn't receive the email.": "Merci de vรฉrifier votre dossier des indรฉsirables si vous n'avez pas reรงu l'email.",
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "Veuillez contacter l'administrateur de cette instance Mobilizon si vous pensez quโ€™il sโ€™agit dโ€™une erreur.",
"Please enter your password to confirm this action.": "Merci d'entrer votre mot de passe pour confirmer cette action.",
"Please make sure the address is correct and that the page hasn't been moved.": "Assurezโ€vous que lโ€™adresse est correcte et que la page nโ€™a pas รฉtรฉ dรฉplacรฉe.",
"Please read the full rules": "Merci de lire les rรจgles complรจtes",
"Please refresh the page and retry.": "Merci de rafraรฎchir la page puis rรฉessayer.",
@ -283,37 +295,37 @@
"Read Framasoftโ€™s statement of intent on the Framablog": "Lire la note dโ€™intention de Framasoft sur le Framablog",
"Redirecting to eventโ€ฆ": "Redirection vers l'รฉvรฉnementโ€ฆ",
"Region": "Rรฉgion",
"Register": "S'inscrire",
"Register an account on Mobilizon!": "S'inscrire sur Mobilizon !",
"Register for an event by choosing one of your identities": "S'inscrire ร  un รฉvรจnement en choisissant une de vos identitรฉs",
"Register": "S'inscrire",
"Registration is allowed, anyone can register.": "Les inscriptions sont autorisรฉes, n'importe qui peut s'inscrire.",
"Registration is closed.": "Les inscriptions sont fermรฉes.",
"Registration is currently closed.": "Les inscriptions sont actuellement fermรฉes.",
"Registrations are restricted by whitelisting.": "Les inscriptions sont restreintes par liste blanche.",
"Reject": "Rejeter",
"Rejected": "Rejetรฉs",
"Rejected participations": "Participations rejetรฉes",
"Rejected": "Rejetรฉs",
"Reopen": "Rรฉouvrir",
"Reply": "Rรฉpondre",
"Report": "Signalement",
"Report this comment": "Signaler ce commentaire",
"Report this event": "Signaler cet รฉvรจnement",
"Reported": "Signalรฉe",
"Reported by": "Signalรฉe par",
"Report": "Signalement",
"Reported by someone on {domain}": "Signalรฉ par quelqu'un depuis {domain}",
"Reported by {reporter}": "Signalรฉ par {reporter}",
"Reported by": "Signalรฉe par",
"Reported identity": "Identitรฉ signalรฉe",
"Reported": "Signalรฉe",
"Reports": "Signalements",
"Requests": "Requรชtes",
"Resend confirmation email": "Envoyer ร  nouveau l'email de confirmation",
"Reset my password": "Rรฉinitialiser mon mot de passe",
"Resolved": "Rรฉsolu",
"Resource provided is not an URL": "La ressource fournie n'est pas une URL",
"Save": "Enregistrer",
"Save draft": "Enregistrer le brouillon",
"Search": "Rechercher",
"Save": "Enregistrer",
"Search events, groups, etc.": "Rechercher des รฉvรจnements, des groupes, etc.",
"Search results: \"{search}\"": "Rรฉsultats de recherche : ยซ {search} ยป",
"Search": "Rechercher",
"Searchingโ€ฆ": "Recherche en coursโ€ฆ",
"Send email": "Envoyer un email",
"Send me an email to reset my password": "Envoyez-moi un email pour rรฉinitialiser mon mot de passe",
@ -333,22 +345,29 @@
"Street": "Rue",
"Tentative: Will be confirmed later": "Provisoire : sera confirmรฉ plus tard",
"Terms": "Conditions d'utilisation",
"The account's email address was changed. Check your emails to verify it.": "L'adresse email du compte a รฉtรฉ modifiรฉe. Vรฉrifiez vos emails pour confirmer le changement.",
"The actual number of participants may differ, as this event is hosted on another instance.": "Le nombre rรฉel de participants peut รชtre diffรฉrent, car cet รฉvรฉnement provient d'une autre instance.",
"The content came from another server. Transfer an anonymous copy of the report?": "Le contenu provient d'une autre instance. Transfรฉrer une copie anonyme du signalement ?",
"The current identity doesn't have any permission on this event. You should probably change it.": "L'identitรฉ actuelle n'a pas de permissions sur cet รฉvรจnement. Vous devriez probablement en changer.",
"The current password is invalid": "Le mot de passe actuel est invalide",
"The draft event has been updated": "L'รฉvรจnement brouillon a รฉtรฉ mis ร  jour",
"The event has been created as a draft": "L'รฉvรจnement a รฉtรฉ crรฉรฉ en tant que brouillon",
"The event has been published": "L'รฉvรจnement a รฉtรฉ publiรฉ",
"The event has been updated": "L'รฉvรจnement a รฉtรฉ mis ร  jour",
"The event has been updated and published": "L'รฉvรจnement a รฉtรฉ mis ร  jour et publiรฉ",
"The event has been updated": "L'รฉvรจnement a รฉtรฉ mis ร  jour",
"The event organizer didn't add any description.": "L'organisateur de l'รฉvรจnement n'a pas ajoutรฉ de description.",
"The event title will be ellipsed.": "Le titre de l'รฉvรจnement sera ellipsรฉ.",
"The new email doesn't seem to be valid": "La nouvelle adresse email ne semble pas รชtre valide",
"The new email must be different": "La nouvelle adresse email doit รชtre diffรฉrente",
"The new password must be different": "Le nouveau mot de passe doit รชtre diffรฉrent",
"The page you're looking for doesn't exist.": "La page que vous recherchez n'existe pas.",
"The password provided is invalid": "Le mot de passe fourni est invalide",
"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 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.",
"The {default_terms} will be used. They will be translated in the user's language.": "Les {default_terms} seront utilisรฉes. Elles seront traduites dans la langue de l'utilisateurโ‹…ice.",
"There are {participants} participants.": "Il n'y a qu'unโ‹…e participantโ‹…e. | Il y a {participants} participants.",
"There will be no way to recover your data.": "Il n'y aura aucun moyen de rรฉcupรฉrer vos donnรฉes.",
"These events may interest you": "Ces รฉvรจnements peuvent vous intรฉresser",
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "Cette instance Mobilizon et l'organisateurโ‹…ice de l'รฉvรฉnement autorise les participations anonymes, mais requiert une validation ร  travers une confirmation par email.",
"This email is already registered as participant for this event": "Cet email est dรฉjร  enregistrรฉ comme participant pour cet รฉvรฉnement",
@ -367,9 +386,9 @@
"URL": "URL",
"Unfortunately, this instance isn't opened to registrations": "Malheureusement, cette instance n'est pas ouverte aux inscriptions",
"Unfortunately, your participation request was rejected by the organizers.": "Malheureusement, votre demande de participation a รฉtรฉ refusรฉe par les organisateurโ‹…ices.",
"Unknown": "Inconnu",
"Unknown actor": "Acteur inconnu",
"Unknown error.": "Erreur inconnue.",
"Unknown": "Inconnu",
"Unsaved changes": "Modifications non enregistrรฉes",
"Upcoming": "ร€ venir",
"Update event {name}": "Mettre ร  jour l'รฉvรจnement {name}",
@ -400,8 +419,8 @@
"You and one other person are going to this event": "Vous รชtes le ou la seule ร  vous rendre ร  cet รฉvรจnement | Vous et une autre personne vous rendez ร  cet รฉvรจnement | Vous et {approved} autres personnes vous rendez ร  cet รฉvรจnement.",
"You are already a participant of this event.": "Vous participez dรฉjร  ร  cet รฉvรจnement.",
"You are already logged-in.": "Vous รชtes dรฉjร  connectรฉ.",
"You are participating in this event anonymously": "Vous participez ร  cet รฉvรฉnement anonymement",
"You are participating in this event anonymously but didn't confirm participation": "Vous participez ร  cet รฉvรฉnement anonymement mais vous n'avez pas confirmรฉ votre participation",
"You are participating in this event anonymously": "Vous participez ร  cet รฉvรฉnement anonymement",
"You can add tags by hitting the Enter key or by adding a comma": "Vous pouvez ajouter des tags en appuyant sur la touche Entrรฉe ou bien en ajoutant une virgule",
"You can try another search term or drag and drop the marker on the map": "Vous pouvez essayer avec d'autres termes de recherche ou bien glisser et dรฉposer le marqueur sur la carte",
"You can't remove your last identity.": "Vous ne pouvez pas supprimer votre derniรจre identitรฉ.",
@ -415,13 +434,18 @@
"You need to login.": "Vous devez vous connecter.",
"You will be redirected to the original instance": "Vous allez รชtre redirigรฉโ‹…e vers l'instance d'origine",
"You wish to participate to the following event": "Vous souhaitez participer ร  l'รฉvรฉnement suivant",
"You'll receive a confirmation email.": "Vous recevrez un email de confirmation.",
"Your account has been successfully deleted": "Votre compte a รฉtรฉ supprimรฉ avec succรจs",
"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 nearly ready, {username}": "Votre compte est presque prรชt, {username}",
"Your current email is {email}. You use it to log in.": "Votre adresse e-mail actuelle est {email}. Vous l'utilisez pour vous connecter.",
"Your email has been changed": "Votre adresse email a bien รฉtรฉ modifiรฉe",
"Your email is being changed": "Votre adresse email est en train d'รชtre modifiรฉe",
"Your email is not whitelisted, you can't register.": "Votre email n'est pas sur la liste blanche, vous ne pouvez pas vous inscrire.",
"Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer.": "Votre email sera uniquement utilisรฉ pour confirmer que vous รชtes bien une personne rรฉelle et vous envoyer des รฉventuelles mises ร  jour pour cet รฉvรฉnement. Il ne sera PAS transmis ร  d'autres instances ou ร  l'organisateur de l'รฉvรฉnement.",
"Your federated identity": "Votre identitรฉ fรฉdรฉrรฉe",
"Your federated identity profile@instance": "Votre identitรฉ fรฉdรฉrรฉe profil@instance",
"Your federated identity": "Votre identitรฉ fรฉdรฉrรฉe",
"Your local administrator resumed its policy:": "Votre administrateur local a rรฉsumรฉ sa politique ainsi :",
"Your participation has been confirmed": "Votre participation a รฉtรฉ confirmรฉe",
"Your participation has been rejected": "Votre participation a รฉtรฉ rejettรฉe",
@ -450,4 +474,4 @@
"{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency.": "{license} garantit {respect} des personnes qui l'utiliseront. Puisque {source}, il est publiquement auditable, ce qui garantit sa transparence.",
"ยฉ The Mobilizon Contributors {date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks": "ยฉ Les contributeurs de Mobilizon {date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines",
"ยฉ The OpenStreetMap Contributors": "ยฉ Les Contributeurโ‹…ices OpenStreetMap"
}
}

@ -7,7 +7,8 @@ import SendPasswordReset from '@/views/User/SendPasswordReset.vue';
import PasswordReset from '@/views/User/PasswordReset.vue';
import { beforeRegisterGuard } from '@/router/guards/register-guard';
import { RouteConfig } from 'vue-router';
import PasswordChange from '@/views/User/PasswordChange.vue';
import AccountSettings from '@/views/User/AccountSettings.vue';
import EmailValidate from '@/views/User/EmailValidate.vue';
export enum UserRouteName {
REGISTER = 'Register',
@ -17,7 +18,7 @@ export enum UserRouteName {
PASSWORD_RESET = 'PasswordReset',
VALIDATE = 'Validate',
LOGIN = 'Login',
PASSWORD_CHANGE = 'PasswordChange',
ACCOUNT_SETTINGS = 'ACCOUNT_SETTINGS',
}
export const userRoutes: RouteConfig[] = [
@ -58,6 +59,13 @@ export const userRoutes: RouteConfig[] = [
meta: { requiresAuth: false },
props: true,
},
{
path: '/validate/email/:token',
name: UserRouteName.VALIDATE,
component: EmailValidate,
props: true,
meta: { requiresAuth: false },
},
{
path: '/validate/:token',
name: UserRouteName.VALIDATE,
@ -73,9 +81,9 @@ export const userRoutes: RouteConfig[] = [
meta: { requiredAuth: false },
},
{
path: '/my-account/password',
name: UserRouteName.PASSWORD_CHANGE,
component: PasswordChange,
path: '/my-account/settings',
name: UserRouteName.ACCOUNT_SETTINGS,
component: AccountSettings,
meta: { requiredAuth: true },
},
];

@ -18,7 +18,7 @@
<div class="identities column is-4">
<identities :currentIdentityName="currentIdentityName" />
<div class="buttons">
<b-button tag="router-link" type="is-secondary" :to="{ name: RouteName.PASSWORD_CHANGE }">{{ $t('Change password') }}</b-button>
<b-button tag="router-link" type="is-secondary" :to="{ name: RouteName.ACCOUNT_SETTINGS }">{{ $t('Account settings') }}</b-button>
</div>
</div>
<div class="column is-8">

@ -0,0 +1,279 @@
<template>
<section class="section container">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li><router-link :to="{ name: RouteName.UPDATE_IDENTITY }">{{ $t('My account') }}</router-link></li>
<li class="is-active"><router-link :to="{ name: RouteName.ACCOUNT_SETTINGS }" aria-current="page">{{ $t('Account settings') }}</router-link></li>
</ul>
</nav>
<div class="setting-title">
<h2>{{ $t('Email') }}</h2>
</div>
<i18n tag="p" class="content" v-if="loggedUser" path="Your current email is {email}. You use it to log in.">
<b slot="email">{{ loggedUser.email }}</b>
</i18n>
<b-notification
type="is-danger"
has-icon
aria-close-label="Close notification"
role="alert"
:key="error"
v-for="error in changeEmailErrors"
>
{{ error }}
</b-notification>
<form @submit.prevent="resetEmailAction" ref="emailForm" class="form">
<b-field :label="$t('New email')">
<b-input
aria-required="true"
required
type="email"
v-model="newEmail"
/>
</b-field>
<p class="help">{{ $t("You'll receive a confirmation email.") }}</p>
<b-field :label="$t('Password')">
<b-input
aria-required="true"
required
type="password"
password-reveal
minlength="6"
v-model="passwordForEmailChange"
/>
</b-field>
<button class="button is-primary" :disabled="!($refs.emailForm && $refs.emailForm.checkValidity())">
{{ $t('Change my email') }}
</button>
</form>
<div class="setting-title">
<h2>{{ $t('Password') }}</h2>
</div>
<b-notification
type="is-danger"
has-icon
aria-close-label="Close notification"
role="alert"
:key="error"
v-for="error in changePasswordErrors"
>
{{ error }}
</b-notification>
<form @submit.prevent="resetPasswordAction" ref="passwordForm" class="form">
<b-field :label="$t('Old password')">
<b-input
aria-required="true"
required
type="password"
password-reveal
minlength="6"
v-model="oldPassword"
/>
</b-field>
<b-field :label="$t('New password')">
<b-input
aria-required="true"
required
type="password"
password-reveal
minlength="6"
v-model="newPassword"
/>
</b-field>
<button class="button is-primary" :disabled="!($refs.passwordForm && $refs.passwordForm.checkValidity())">
{{ $t('Change my password') }}
</button>
</form>
<div class="setting-title">
<h2>{{ $t('Delete account') }}</h2>
</div>
<p class="content">{{ $t('Deleting my account will delete all of my identities.')}}</p>
<b-button @click="openDeleteAccountModal" type="is-danger">{{ $t('Delete my account') }}</b-button>
<b-modal :active.sync="isDeleteAccountModalActive"
has-modal-card full-screen :can-cancel="false">
<section class="hero is-primary is-fullheight">
<div class="hero-body has-text-centered">
<div class="container">
<div class="columns">
<div class="column is-one-third-desktop is-offset-one-third-desktop">
<h1 class="title">
{{ $t('Deleting your Mobilizon account') }}
</h1>
<p class="content">
{{ $t("Are you really sure you want to delete your whole account? You'll lose everything. Identities, settings, events created, messages and participations will be gone forever.") }}
<br>
<b>{{ $t('There will be no way to recover your data.') }}</b>
</p>
<p class="content">{{ $t('Please enter your password to confirm this action.')}}</p>
<form @submit.prevent="deleteAccount">
<b-field>
<b-input type="password" v-model="passwordForAccountDeletion" password-reveal icon="lock" :placeholder="$t('Password')"/>
</b-field>
<b-button native-type="submit" type="is-danger" size="is-large">{{ $t('Delete everything') }}</b-button>
</form>
<div class="cancel-button">
<b-button type="is-light" @click="isDeleteAccountModalActive = false">{{ $t('Cancel') }}</b-button>
</div>
</div>
</div>
</div>
</div>
</section>
</b-modal>
</section>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { CHANGE_EMAIL, CHANGE_PASSWORD, DELETE_ACCOUNT, LOGGED_USER } from '@/graphql/user';
import { RouteName } from '@/router';
import { Refs } from '@/shims-vue';
import { ICurrentUser } from '@/types/current-user.model';
import { logout } from '@/utils/auth';
@Component({
apollo: {
loggedUser: LOGGED_USER,
},
})
export default class AccountSettings extends Vue {
$refs!: Refs<{
passwordForm: HTMLElement,
}>;
loggedUser!: ICurrentUser;
passwordForEmailChange: string = '';
newEmail: string = '';
changeEmailErrors: string[] = [];
oldPassword: string = '';
newPassword: string = '';
changePasswordErrors: string[] = [];
isDeleteAccountModalActive: boolean = false;
passwordForAccountDeletion: string = '';
RouteName = RouteName;
async resetEmailAction() {
this.changeEmailErrors = [];
try {
await this.$apollo.mutate({
mutation: CHANGE_EMAIL,
variables: {
email: this.newEmail,
password: this.passwordForEmailChange,
},
});
this.$notifier.info(this.$t("The account's email address was changed. Check your emails to verify it.") as string);
this.newEmail = '';
this.passwordForEmailChange = '';
} catch (err) {
this.handleErrors('email', err);
}
}
async resetPasswordAction() {
this.changePasswordErrors = [];
try {
await this.$apollo.mutate({
mutation: CHANGE_PASSWORD,
variables: {
oldPassword: this.oldPassword,
newPassword: this.newPassword,
},
});
this.$notifier.success(this.$t('The password was successfully changed') as string);
} catch (err) {
this.handleErrors('password', err);
}
}
protected async openDeleteAccountModal() {
this.passwordForAccountDeletion = '';
this.isDeleteAccountModalActive = true;
}
async deleteAccount() {
try {
await this.$apollo.mutate({
mutation: DELETE_ACCOUNT,
variables: {
password: this.passwordForAccountDeletion,
},
});
await logout(this.$apollo.provider.defaultClient);
this.$buefy.notification.open({
message: this.$t('Your account has been successfully deleted') as string,
type: 'is-success',
position: 'is-bottom-right',
duration: 5000,
});
return await this.$router.push({ name: RouteName.HOME });
} catch (err) {
this.handleErrors('delete', err);
}
}
private handleErrors(type: string, err: any) {
console.error(err);
if (err.graphQLErrors !== undefined) {
err.graphQLErrors.forEach(({ message }) => {
switch (type) {
case 'email':
this.changeEmailErrors.push(this.convertMessage(message) as string);
break;
case 'password':
this.changePasswordErrors.push(this.convertMessage(message) as string);
break;
}
});
}
}
private convertMessage(message: string) {
switch (message) {
case 'The password provided is invalid':
return this.$t('The password provided is invalid');
case 'The new email must be different':
return this.$t('The new email must be different');
case "The new email doesn't seem to be valid":
return this.$t("The new email doesn't seem to be valid");
case 'The current password is invalid':
return this.$t('The current password is invalid');
case 'The new password must be different':
return this.$t('The new password must be different');
}
}
}
</script>
<style lang="scss" scoped>
@import "@/variables.scss";
.setting-title {
margin-top: 3rem;
h2 {
display: inline;
background: $secondary;
padding: 2px 7.5px;
text-transform: uppercase;
font-size: 1.25rem;
}
}
.cancel-button {
margin-top: 2rem;
}
/deep/ .modal .modal-background {
background-color: initial;
}
</style>

@ -0,0 +1,52 @@
<template>
<section class="section container">
<h1 class="title" v-if="loading">
{{ $t('Your email is being changed') }}
</h1>
<div v-else>
<div v-if="failed">
<b-message :title="$t('Error while changing email')" type="is-danger">
{{ $t('Either the email has already been changed, either the validation token is incorrect.') }}
</b-message>
</div>
<h1 class="title" v-else>
{{ $t('Your email has been changed') }}
</h1>
</div>
</section>
</template>
<script lang="ts">
import { VALIDATE_EMAIL } from '@/graphql/user';
import { Component, Prop, Vue } from 'vue-property-decorator';
import { RouteName } from '@/router';
@Component
export default class Validate extends Vue {
@Prop({ type: String, required: true }) token!: string;
loading = true;
failed = false;
async created() {
await this.validateAction();
}
async validateAction() {
try {
await this.$apollo.mutate<{ validateEmail }>({
mutation: VALIDATE_EMAIL,
variables: {
token: this.token,
},
});
this.loading = false;
return await this.$router.push({ name: RouteName.HOME });
} catch (err) {
console.error(err);
this.failed = true;
}
}
}
</script>

@ -1,92 +0,0 @@
<template>
<section class="section container">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li><router-link :to="{ name: RouteName.UPDATE_IDENTITY }">{{ $t('My account') }}</router-link></li>
<li class="is-active"><router-link :to="{ name: RouteName.PASSWORD_CHANGE }" aria-current="page">{{ $t('Password change') }}</router-link></li>
</ul>
</nav>
<h1 class="title">{{ $t('Password') }}</h1>
<b-notification
type="is-danger"
has-icon
aria-close-label="Close notification"
role="alert"
:key="error"
v-for="error in errors"
>
{{ error }}
</b-notification>
<form @submit="resetAction" class="form">
<b-field :label="$t('Old password')">
<b-input
aria-required="true"
required
type="password"
password-reveal
minlength="6"
v-model="oldPassword"
/>
</b-field>
<b-field :label="$t('New password')">
<b-input
aria-required="true"
required
type="password"
password-reveal
minlength="6"
v-model="newPassword"
/>
</b-field>
<button class="button is-primary">
{{ $t('Change my password') }}
</button>
</form>
</section>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { CHANGE_PASSWORD } from '@/graphql/user';
import { RouteName } from '@/router';
@Component
export default class PasswordChange extends Vue {
oldPassword: string = '';
newPassword: string = '';
errors: string[] = [];
RouteName = RouteName;
async resetAction(e) {
e.preventDefault();
this.errors = [];
try {
await this.$apollo.mutate({
mutation: CHANGE_PASSWORD,
variables: {
oldPassword: this.oldPassword,
newPassword: this.newPassword,
},
});
this.$notifier.success(this.$t('The password was successfully changed') as string);
} catch (err) {
this.handleError(err);
}
}
private handleError(err: any) {
console.error(err);
if (err.graphQLErrors !== undefined) {
err.graphQLErrors.forEach(({ message }) => {
this.errors.push(message);
});
}
}
}
</script>
<style lang="scss">
</style>

@ -20,7 +20,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do
%{context: %{current_user: %User{id: id} = user}}
) do
with {:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
{:ok, feed_token} <- Events.create_feed_token(%{"user_id" => id, "actor_id" => actor_id}) do
{:ok, feed_token} <- Events.create_feed_token(%{user_id: id, actor_id: actor_id}) do
{:ok, feed_token}
else
{:is_owned, nil} ->
@ -33,7 +33,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do
"""
@spec create_feed_token(any, map, map) :: {:ok, FeedToken.t()}
def create_feed_token(_parent, %{}, %{context: %{current_user: %User{id: id}}}) do
with {:ok, feed_token} <- Events.create_feed_token(%{"user_id" => id}) do
with {:ok, feed_token} <- Events.create_feed_token(%{user_id: id}) do
{:ok, feed_token}
end
end

@ -7,6 +7,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
alias Mobilizon.{Actors, Config, Events, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Crypto
alias Mobilizon.Storage.Repo
alias Mobilizon.Users.User
@ -14,6 +15,8 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
require Logger
@confirmation_token_length 30
@doc """
Find an user by its ID
"""
@ -298,4 +301,82 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
def change_password(_parent, _args, _resolution) do
{:error, "You need to be logged-in to change your password"}
end
def change_email(_parent, %{email: new_email, password: password}, %{
context: %{current_user: %User{email: old_email, password_hash: password_hash} = user}
}) do
with {:current_password, true} <-
{:current_password, Argon2.verify_pass(password, password_hash)},
{:same_email, false} <- {:same_email, new_email == old_email},
{:email_valid, true} <- {:email_valid, Email.Checker.valid?(new_email)},
{:ok, %User{} = user} <-
user
|> User.changeset(%{
unconfirmed_email: new_email,
confirmation_token: Crypto.random_string(@confirmation_token_length),
confirmation_sent_at: DateTime.utc_now() |> DateTime.truncate(:second)
})
|> Repo.update() do
user
|> Email.User.send_email_reset_old_email()
|> Email.Mailer.deliver_later()
user
|> Email.User.send_email_reset_new_email()
|> Email.Mailer.deliver_later()
{:ok, user}
else
{:current_password, false} ->
{:error, "The password provided is invalid"}
{:same_email, true} ->
{:error, "The new email must be different"}
{:email_valid, _} ->
{:error, "The new email doesn't seem to be valid"}
end
end
def change_email(_parent, _args, _resolution) do
{:error, "You need to be logged-in to change your email"}
end
def validate_email(_parent, %{token: token}, _resolution) do
with %User{} = user <- Users.get_user_by_activation_token(token),
{:ok, %User{} = user} <-
user
|> User.changeset(%{
email: user.unconfirmed_email,
unconfirmed_email: nil,
confirmation_token: nil,
confirmation_sent_at: nil
})
|> Repo.update() do
{:ok, user}
end
end
def delete_account(_parent, %{password: password}, %{
context: %{current_user: %User{password_hash: password_hash} = user}
}) do
with {:current_password, true} <-
{:current_password, Argon2.verify_pass(password, password_hash)},
actors <- Users.get_actors_for_user(user),
# Detach actors from user
:ok <- Enum.each(actors, fn actor -> Actors.update_actor(actor, %{user_id: nil}) end),
# Launch a background job to delete actors
:ok <- Enum.each(actors, &Actors.delete_actor/1),
# Delete user
{:ok, user} <- Users.delete_user(user) do
{:ok, user}
else
{:current_password, false} ->
{:error, "The password provided is invalid"}
end
end
def delete_account(_parent, _args, _resolution) do
{:error, "You need to be logged-in to delete your account"}
end
end

@ -178,5 +178,21 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
arg(:new_password, non_null(:string))
resolve(&User.change_password/3)
end
field :change_email, :user do
arg(:email, non_null(:string))
arg(:password, non_null(:string))
resolve(&User.change_email/3)
end
field :validate_email, :user do
arg(:token, non_null(:string))
resolve(&User.validate_email/3)
end
field :delete_account, :deleted_object do
arg(:password, non_null(:string))
resolve(&User.delete_account/3)
end
end
end

@ -186,7 +186,7 @@ defmodule Mobilizon.Actors do
%Actor{}
|> Actor.registration_changeset(args)
|> Repo.insert() do
Events.create_feed_token(%{"user_id" => args["user_id"], "actor_id" => person.id})
Events.create_feed_token(%{user_id: args["user_id"], actor_id: person.id})
{:ok, person}
end

@ -1367,7 +1367,7 @@ defmodule Mobilizon.Events do
"""
@spec create_feed_token(map) :: {:ok, FeedToken.t()} | {:error, Changeset.t()}
def create_feed_token(attrs \\ %{}) do
attrs = Map.put(attrs, "token", Ecto.UUID.generate())
attrs = Map.put(attrs, :token, Ecto.UUID.generate())
%FeedToken{}
|> FeedToken.changeset(attrs)

@ -39,11 +39,12 @@ defmodule Mobilizon.Users.User do
:confirmation_token,
:reset_password_sent_at,
:reset_password_token,
:locale
:locale,
:unconfirmed_email
]
@attrs @required_attrs ++ @optional_attrs
@registration_required_attrs [:email, :password]
@registration_required_attrs @required_attrs ++ [:password]
@password_change_required_attrs [:password]
@password_reset_required_attrs @password_change_required_attrs ++
@ -61,6 +62,7 @@ defmodule Mobilizon.Users.User do
field(:confirmation_token, :string)
field(:reset_password_sent_at, :utc_datetime)
field(:reset_password_token, :string)
field(:unconfirmed_email, :string)
field(:locale, :string, default: "en")
belongs_to(:default_actor, Actor)
@ -99,7 +101,7 @@ defmodule Mobilizon.Users.User do
|> save_confirmation_token()
|> unique_constraint(
:confirmation_token,
message: "The registration is already in use, this looks like an issue on our side."
message: "The registration token is already in use, this looks like an issue on our side."
)
end

@ -31,7 +31,7 @@ defmodule Mobilizon.Users do
%User{}
|> User.registration_changeset(args)
|> Repo.insert() do
Events.create_feed_token(%{"user_id" => user.id})
Events.create_feed_token(%{user_id: user.id})
{:ok, user}
end
@ -267,7 +267,10 @@ defmodule Mobilizon.Users do
@spec user_by_email_query(String.t(), boolean | nil) :: Ecto.Query.t()
defp user_by_email_query(email, nil) do
from(u in User, where: u.email == ^email, preload: :default_actor)
from(u in User,
where: u.email == ^email or u.unconfirmed_email == ^email,
preload: :default_actor
)
end
defp user_by_email_query(email, true) do
@ -281,7 +284,7 @@ defmodule Mobilizon.Users do
defp user_by_email_query(email, false) do
from(
u in User,
where: u.email == ^email and is_nil(u.confirmed_at),
where: (u.email == ^email or u.unconfirmed_email == ^email) and is_nil(u.confirmed_at),
preload: :default_actor
)
end

@ -61,9 +61,10 @@ defmodule Mobilizon.Web.Email.User do
with %User{} = user <- Users.get_user_by_activation_token(token),