From e7466768e919d3c05ed32273b3e20eea92ae417d Mon Sep 17 00:00:00 2001 From: julien-vendran Date: Sun, 11 Jul 2021 14:04:46 +0200 Subject: [PATCH 1/7] mail_template/ : Adding a template to user who just registered. Allow user to create their account by their own. src/account/ : Adding a function to create an account. src/mailing/ : Adding a function to send an email to user who just registered. src/model/ : Adding a column to know if a user is confirmed by a specific member or not. --- mail-templates/confirm_registration.html | 119 +++++++++++++++++++++++ src/account/account.resolver.ts | 30 +++++- src/account/account.service.ts | 31 ++++++ src/mailing/mailing.service.ts | 18 ++++ src/models/resource-owner.entity.ts | 3 + 5 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 mail-templates/confirm_registration.html diff --git a/mail-templates/confirm_registration.html b/mail-templates/confirm_registration.html new file mode 100644 index 0000000..f9c20a6 --- /dev/null +++ b/mail-templates/confirm_registration.html @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + +
+ Logo du site +

BDE Polytech

+
+ +
+ +

+ Bonjour,
+
+ Votre inscription a été prise en compte, elle est maintenant en attente de validation par un membre du BDE. + Dès lors que votre compte sera validé, vous recevrez un mail pour finaliser votre inscription. +

+ +

+ Vous pourrez, par la suite, profiter de toutes les fonctionnalités du site ! +

+ +
+ +
+ + + + + + diff --git a/src/account/account.resolver.ts b/src/account/account.resolver.ts index 327129b..e268c09 100644 --- a/src/account/account.resolver.ts +++ b/src/account/account.resolver.ts @@ -1,4 +1,4 @@ -import { Args, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { Args, Parent, Query, ResolveField, Resolver, Mutation } from '@nestjs/graphql'; import { UserType } from '../graphql/types/user.gql'; import { AccountService } from './account.service'; import { BdeResolver } from '../bde/bde.resolver'; @@ -7,6 +7,7 @@ import { UseGuards } from '@nestjs/common'; import { AuthGuard } from '../oauth/guard/auth.guard'; import { User } from '../oauth/decorator/user.decorator'; import { ResourceOwner } from '../models/resource-owner.entity'; +import { BdeAdminDto, SpecialtyDto } from '../bde/dto/create-bde.dto'; @Resolver(() => UserType) export class AccountResolver { @@ -37,6 +38,33 @@ export class AccountResolver { return UserType.fromResourceOwnerModel(resourceOwner); } + @Mutation(() => UserType, { name: 'newAccount' }) + async createAccountToValidate ( + @Args('mail') + mail: string, + @Args('firstN') + firstN: string, + @Args('lastN') + lastN: string, + ) { + console.log("On passe "); + //Assume that only one BDE id there + this.bdeResolver.getBDEs() + .then(bde => { + console.log(bde[0].id); + + this.accountService.createAccountToValidate(mail, firstN, lastN, bde[0].id) + .then(result => { + return UserType.fromResourceOwnerModel(result); + }); + } + ) + .catch(() => { + console.log("Une erreur est survenue dans la récupération des informations du BDE."); + }) + //console.log(await this.bdeResolver.getBDE(await this.bdeResolver.getBDEs()[0].id)); + } + @ResolveField('bde', () => BdeType) async getUserBde(@Parent() user: UserType) { return this.bdeResolver.getBDE(user.bdeId); diff --git a/src/account/account.service.ts b/src/account/account.service.ts index 93a2d4d..0d53c96 100644 --- a/src/account/account.service.ts +++ b/src/account/account.service.ts @@ -81,6 +81,37 @@ export class AccountService { return user; } + async createAccountToValidate ( + mail: string, + firstN: string, + lastN: string, + bdeId: string + ): Promise { + + let user = new ResourceOwner(); + user.email = mail.toLowerCase(); + user.firstname = firstN; + user.lastname = lastN; + user.roles = []; + user.bdeId = bdeId; + user.membershipDate = null; + + try { + user = await this.resourceOwnerRepository.save(user); + } catch (e) { + if (e.constraint && e.constraint === UQ_EMAIL_CONSTRAINT) { + throw new BadRequestException('An user with this email already exists'); + } + throw new InternalServerErrorException('Unable to create account'); + } + + try { + await this.mailingService.sendFirstPartRegistrationMail(user); + } catch {} + + return user; + } + async updateAccount( userId: string, userData: { diff --git a/src/mailing/mailing.service.ts b/src/mailing/mailing.service.ts index 854492b..070b46f 100644 --- a/src/mailing/mailing.service.ts +++ b/src/mailing/mailing.service.ts @@ -46,6 +46,24 @@ export class MailingService { return this.config.get(SITE_URL); } + /** + * Send a mail to confirm the sign in of the user. The account have to be validate by a specific member before adding a password. + * @param user is the user who will receive the email. + */ + async sendFirstPartRegistrationMail(user: ResourceOwner) { + this.logger.debug(`Sending first part registration email to ${user.email}`); + const renderedTemplate = await this.renderTemplate('confirm_registration.html', { + frontURL: this.getFrontURL(), + }); + + await this.sendMail({ + to: user.email, + subject: 'Validation de votre inscription', + text: `Votre compte est maintenant en attente de validation, nous nous efforçons de minimiser les délais.`, + html: renderedTemplate, + }); + } + async sendRegistrationMail(user: ResourceOwner) { this.logger.debug(`Sending registration email to ${user.email}`); const registerURL = `${this.getFrontURL()}/compte/reset-password?token=${ diff --git a/src/models/resource-owner.entity.ts b/src/models/resource-owner.entity.ts index aac4e3e..c135ed6 100644 --- a/src/models/resource-owner.entity.ts +++ b/src/models/resource-owner.entity.ts @@ -62,6 +62,9 @@ export class ResourceOwner { @Column() bdeId: string; + + @Column('boolean', { default: false }) + isValidateMember: boolean = false; // Relationships From 50348129df1655d9a7c32e87a92fa30429a00c5b Mon Sep 17 00:00:00 2001 From: julien-vendran Date: Thu, 29 Jul 2021 19:28:59 +0200 Subject: [PATCH 2/7] Adding roles in userType --- src/graphql/types/user.gql.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/graphql/types/user.gql.ts b/src/graphql/types/user.gql.ts index af65679..f9dc5fb 100644 --- a/src/graphql/types/user.gql.ts +++ b/src/graphql/types/user.gql.ts @@ -14,6 +14,10 @@ export class UserType { @Field() email: string; + //By using this we can know all access of the connected user + @Field(type => [String]) + roles: string[]; + @Field() bdeId: string; @@ -30,6 +34,7 @@ export class UserType { const user = new UserType(); user.firstname = ro.firstname; user.lastname = ro.lastname; + user.roles = ro.roles; user.email = ro.email; user.bdeId = ro.bdeId; if (ro.specialtyName) { From 3f1bbf8ec238fc6c64760dafd90b7ddb59f8d408 Mon Sep 17 00:00:00 2001 From: julien-vendran Date: Thu, 29 Jul 2021 19:30:06 +0200 Subject: [PATCH 3/7] First version of the request to ask all user not validate yet --- src/account/account.resolver.ts | 28 ++++++++++------ src/account/account.service.ts | 57 ++++++++++++++++++++++++++++++--- 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/src/account/account.resolver.ts b/src/account/account.resolver.ts index e268c09..7e5b205 100644 --- a/src/account/account.resolver.ts +++ b/src/account/account.resolver.ts @@ -7,7 +7,6 @@ import { UseGuards } from '@nestjs/common'; import { AuthGuard } from '../oauth/guard/auth.guard'; import { User } from '../oauth/decorator/user.decorator'; import { ResourceOwner } from '../models/resource-owner.entity'; -import { BdeAdminDto, SpecialtyDto } from '../bde/dto/create-bde.dto'; @Resolver(() => UserType) export class AccountResolver { @@ -49,19 +48,20 @@ export class AccountResolver { ) { console.log("On passe "); //Assume that only one BDE id there - this.bdeResolver.getBDEs() - .then(bde => { - console.log(bde[0].id); + return this.bdeResolver.getBDEs() + .then(async bde => { + console.log(bde[0].id); - this.accountService.createAccountToValidate(mail, firstN, lastN, bde[0].id) - .then(result => { - return UserType.fromResourceOwnerModel(result); - }); + const result = await this.accountService.createAccountToValidate(mail, firstN, lastN, bde[0].id); //Assume that there is only one BDE. + return UserType.fromResourceOwnerModel(result); } ) - .catch(() => { + .catch((e) => { + console.error(e); console.log("Une erreur est survenue dans la récupération des informations du BDE."); - }) + const err = new ResourceOwner(); + return UserType.fromResourceOwnerModel(err); + }); //console.log(await this.bdeResolver.getBDE(await this.bdeResolver.getBDEs()[0].id)); } @@ -69,4 +69,12 @@ export class AccountResolver { async getUserBde(@Parent() user: UserType) { return this.bdeResolver.getBDE(user.bdeId); } + + /** + * Get all user who are not validate yet. + */ + @Query(() => [UserType], {name: 'allUsers'}) + async getAllNonValidateUsersQ () { + return this.accountService.getAllNonValidateUsers(); + } } diff --git a/src/account/account.service.ts b/src/account/account.service.ts index 0d53c96..306dffc 100644 --- a/src/account/account.service.ts +++ b/src/account/account.service.ts @@ -66,7 +66,7 @@ export class AccountService { user.roles = userData.roles; this.updateResetPasswordToken(user, 30, 'd'); - try { + try { //TODO : Gérer ces erreurs pour qu'il s'affiche correctement en front user = await this.resourceOwnerRepository.save(user); } catch (e) { if (e.constraint && e.constraint === UQ_EMAIL_CONSTRAINT) { @@ -87,6 +87,7 @@ export class AccountService { lastN: string, bdeId: string ): Promise { + let local = true; //Disbale mail let user = new ResourceOwner(); user.email = mail.toLowerCase(); @@ -105,9 +106,11 @@ export class AccountService { throw new InternalServerErrorException('Unable to create account'); } - try { - await this.mailingService.sendFirstPartRegistrationMail(user); - } catch {} + if ( ! local) { + try { + await this.mailingService.sendFirstPartRegistrationMail(user); + } catch {} + } return user; } @@ -183,20 +186,51 @@ export class AccountService { return { valid: true }; } + /** + * Check if the account associate with this token is validate by a specific member. + * + * @param token The reset-apssword token + * @returns true if the account is a valid account, false otherwise + */ + async checkValidateAccount (token: String) { + const resourceOwner = await this.resourceOwnerRepository.findOne({ + where: { + resetPasswordToken: token, + }, + }); + if ( + !resourceOwner || + resourceOwner.isValidateMember == false + ) { + return { + valid: false + }; + } + return { + valid: true + }; + } + /** * Sets the given password as the new password for the account the reset-password * token was generated for. * * If the token is expired, a BadRequestException is thrown. + * If the account has not yet been validate, a BadRequestException is thrown. * * @param token The reset-password token the user received by email * @param password The new password to set * @returns */ async resetPassword(token: string, password: string) { + this.logger.debug("Starting the reset password process ..."); const valid = (await this.checkResetPasswordToken(token)).valid; + const validAcc = (await this.checkValidateAccount(token)).valid; + this.logger.debug("Compte validé : " + validAcc + "."); if (!valid) { throw new BadRequestException('The given token is invalid'); + } else if (!validAcc) { + throw new BadRequestException('The account has not yet been validated'); } const newPassword = await this.passwordService.hashPassword(password); await this.resourceOwnerRepository.update( @@ -209,6 +243,7 @@ export class AccountService { password: newPassword, }, ); + this.logger.debug("The new password is now configured ..."); return { ok: true }; } @@ -239,6 +274,7 @@ export class AccountService { * * If no account with the given email address was found, this method * is a noop but do not fails. + * If the given email is not validate yet, the method throw a BadRequestException. * * @param email The email of the account to reset password of */ @@ -249,7 +285,7 @@ export class AccountService { }, }); - if (ro) { + if (ro && ro.isValidateMember == true) { this.updateResetPasswordToken(ro); try { await this.resourceOwnerRepository.update(ro.id, { @@ -261,8 +297,19 @@ export class AccountService { this.logger.error(e); throw new InternalServerErrorException({ ok: false }); } + } else { + throw new BadRequestException({ + msg: 'The account has not yet been validated', + ok: false, + }); } return { ok: true }; } + + + getAllNonValidateUsers() { + this.logger.debug("Searching all non validate users ..."); + return this.resourceOwnerRepository.find(); + } } From e69ae4ab27ed8b99eb674e38960cdfc73d321ec4 Mon Sep 17 00:00:00 2001 From: julien-vendran Date: Wed, 25 Aug 2021 18:16:28 +0200 Subject: [PATCH 4/7] =?UTF-8?q?Mise=20=C3=A0=20jour=20des=20pieds=20de=20p?= =?UTF-8?q?ages=20des=20templates=20existants.=20account-nonvalidate.html?= =?UTF-8?q?=20:=20Template=20de=20mail=20pour=20le=20refus=20d'un=20utilis?= =?UTF-8?q?ateur.=20account=5Fvalidate.html=20:=20Template=20envoy=C3=A9?= =?UTF-8?q?=20si=20l'inscription=20est=20valid=C3=A9e=20par=20un=20admin.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mail-templates/account-nonvalidate.html | 113 +++++++++++++++++++++++ mail-templates/account_validate.html | 116 ++++++++++++++++++++++++ mail-templates/registration.html | 28 ++++-- mail-templates/reset-password.html | 28 ++++-- 4 files changed, 273 insertions(+), 12 deletions(-) create mode 100644 mail-templates/account-nonvalidate.html create mode 100644 mail-templates/account_validate.html diff --git a/mail-templates/account-nonvalidate.html b/mail-templates/account-nonvalidate.html new file mode 100644 index 0000000..81b5068 --- /dev/null +++ b/mail-templates/account-nonvalidate.html @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + +
+ Logo du site +

BDE Polytech

+
+ +
+ +

+ Votre demande de création de compte a été refusée par un membre du BDE ... +

+ +

+ Si vous êtes un membre de Polytech Montpellier, veillez à ce que les informations rentrées correspondent à celle connus par l'école. + Si vous ne comprennez pas votre refus, contactez un membre du BDE. +

+ +
+ +
+ + + + + + diff --git a/mail-templates/account_validate.html b/mail-templates/account_validate.html new file mode 100644 index 0000000..cf402ed --- /dev/null +++ b/mail-templates/account_validate.html @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + +
+ Logo du site +

BDE Polytech

+
+ +
+ +

+ Votre compte a été validé par un membre du BDE. Vous pouvez maintenant profiter de toutes les fonctionnalités du site ! +

+ +

+ Il faut maintenant ajouter un mot de passe à votre compte en cliquant sur le bouton ci-dessous. +

+ +
+ + Changer de mot de passe + +
+ +
+ + + + + + diff --git a/mail-templates/registration.html b/mail-templates/registration.html index c1b223a..dd10495 100644 --- a/mail-templates/registration.html +++ b/mail-templates/registration.html @@ -89,13 +89,29 @@

BDE Polytech


diff --git a/mail-templates/reset-password.html b/mail-templates/reset-password.html index 6b12f8a..c7ebf1c 100644 --- a/mail-templates/reset-password.html +++ b/mail-templates/reset-password.html @@ -87,13 +87,29 @@

BDE Polytech


From 372d714ac72abd54582e9c9ef0add46ec93dd90c Mon Sep 17 00:00:00 2001 From: julien-vendran Date: Wed, 25 Aug 2021 18:18:09 +0200 Subject: [PATCH 5/7] Ajout de fonction pour le fonctionnement de la validation / refus des nouveaux utilisateurs. --- src/account/account.resolver.ts | 52 ++++++++++++++++++++++++++- src/account/account.service.ts | 63 ++++++++++++++++++++++++++++++--- 2 files changed, 110 insertions(+), 5 deletions(-) diff --git a/src/account/account.resolver.ts b/src/account/account.resolver.ts index 7e5b205..cedc411 100644 --- a/src/account/account.resolver.ts +++ b/src/account/account.resolver.ts @@ -58,7 +58,7 @@ export class AccountResolver { ) .catch((e) => { console.error(e); - console.log("Une erreur est survenue dans la récupération des informations du BDE."); + console.error("Une erreur est survenue dans la récupération des informations du BDE."); const err = new ResourceOwner(); return UserType.fromResourceOwnerModel(err); }); @@ -77,4 +77,54 @@ export class AccountResolver { async getAllNonValidateUsersQ () { return this.accountService.getAllNonValidateUsers(); } + + /** + * Set a user to validate. + * @param mail is the mail of the user we want to validate + */ + @Mutation(() => UserType, { name: 'validateUser' }) + async ValidateAnAccount ( + @Args('mail') + mail: string, + ) { + console.log("Notre mail reçu : " + mail); + return await this.accountService.getAccountByEmail(mail) + .then(async user => { + const result = await this.accountService.validateUser(user); + console.log("La validation de l'utilisateur a bien été effectuée ..."); + return UserType.fromResourceOwnerModel(result); + } + ) + .catch((e) => { + console.error(e); + console.error("Une erreur est survenue dans la récupération des informations du BDE ..."); + const err = new ResourceOwner(); + return UserType.fromResourceOwnerModel(err); + }); + } + + /** + * Delete users information from database. Inform the users by email. + * @param mail is the mail of the user we want to validate + */ + @Mutation(() => UserType, { name: 'deleteUser' }) + async UnvalidateAnAccount ( + @Args('mail') + mail: string, + ) { + console.log("Notre mail reçu : " + mail); + return await this.accountService.getAccountByEmail(mail) + .then(async user => { + const result = await this.accountService.unvalidateUser(user); + console.log("La suppression de l'utilisateur a bien été effectuée ..."); + return UserType.fromResourceOwnerModel(result); + } + ) + .catch((e) => { + console.error(e); + console.error("Une erreur est survenue dans la récupération des informations du BDE ..."); + const err = new ResourceOwner(); + return UserType.fromResourceOwnerModel(err); + }); + } } diff --git a/src/account/account.service.ts b/src/account/account.service.ts index 306dffc..baa7d26 100644 --- a/src/account/account.service.ts +++ b/src/account/account.service.ts @@ -164,6 +164,18 @@ export class AccountService { return resourceOwner; } + async getAccountByEmail(mail: string) { + const resourceOwner = await this.resourceOwnerRepository.findOne({ + where: { + email: mail + } + }); + if (!resourceOwner) { + throw new NotFoundException('No account with the given email can be found.'); + } + return resourceOwner; + } + /** * Checks if the given reset-password is linked to an existing account * and is not expired. @@ -189,7 +201,7 @@ export class AccountService { /** * Check if the account associate with this token is validate by a specific member. * - * @param token The reset-apssword token + * @param token The reset-password token * @returns true if the account is a valid account, false otherwise */ async checkValidateAccount (token: String) { @@ -200,7 +212,7 @@ export class AccountService { }); if ( !resourceOwner || - resourceOwner.isValidateMember == false + resourceOwner.isvalidatemember == false ) { return { valid: false @@ -285,7 +297,7 @@ export class AccountService { }, }); - if (ro && ro.isValidateMember == true) { + if (ro && ro.isvalidatemember == true) { this.updateResetPasswordToken(ro); try { await this.resourceOwnerRepository.update(ro.id, { @@ -310,6 +322,49 @@ export class AccountService { getAllNonValidateUsers() { this.logger.debug("Searching all non validate users ..."); - return this.resourceOwnerRepository.find(); + return this.resourceOwnerRepository.find( + { + where: { + isvalidatemember: false, + } + } + ); + } + + async validateUser(user: ResourceOwner) { + this.logger.debug("Validation of a new user ..."); + + this.updateResetPasswordToken(user, 30, 'd'); + user.isvalidatemember = true; + + try { + user = await this.resourceOwnerRepository.save(user); + } catch (e) { + throw new InternalServerErrorException('Unable to create account'); + } + + //Send registration mail + try { + await this.mailingService.sendValidateAccountMail(user); + } catch {} + + return user; + } + + async unvalidateUser(user: ResourceOwner) { + this.logger.debug("Suppresion of a new user ..."); + + try { + user = await this.resourceOwnerRepository.remove(user); + } catch (e) { + throw new InternalServerErrorException('Unable to delete an account'); + } + + //Send registration mail + try { + await this.mailingService.sendUnvalidateAccountMail(user); + } catch {} + + return user; } } From 4e32c2a3073223147754d6a243934f90af976425 Mon Sep 17 00:00:00 2001 From: julien-vendran Date: Wed, 25 Aug 2021 18:20:00 +0200 Subject: [PATCH 6/7] Ajout de fonction pour l'envoi des nouveaux template de mail. --- src/mailing/mailing.service.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/mailing/mailing.service.ts b/src/mailing/mailing.service.ts index 070b46f..24ee2cb 100644 --- a/src/mailing/mailing.service.ts +++ b/src/mailing/mailing.service.ts @@ -100,6 +100,38 @@ export class MailingService { }); } + async sendValidateAccountMail(user: ResourceOwner) { + this.logger.debug(`Sending validate account email to ${user.email}`); + const resetURL = `${this.getFrontURL()}/compte/reset-password?token=${ + user.resetPasswordToken + }`; + const renderedTemplate = await this.renderTemplate('account_validate.html', { + resetURL, + frontURL: this.getFrontURL(), + }); + + await this.sendMail({ + to: user.email, + subject: 'Votre compte à été validé !', + text: `Ajoutez un mot de passe à l'adresse suivante: ${resetURL}`, + html: renderedTemplate, + }); + } + + async sendUnvalidateAccountMail(user: ResourceOwner) { + this.logger.debug(`Sending unvalidate account email to ${user.email}`); + const renderedTemplate = await this.renderTemplate('account-nonvalidate.html', { + frontURL: this.getFrontURL(), + }); + + await this.sendMail({ + to: user.email, + subject: 'Votre compte à été refusé ...', + text: `Nous ne pouvons pas donner suite à votre demande.`, + html: renderedTemplate, + }); + } + /** * Retrieves template content then replace each occurrence of * `{{key}}` with the value of `values[key]`. From 6e2b596c3b809f5bde1f431e9f630b1f919cd91d Mon Sep 17 00:00:00 2001 From: julien-vendran Date: Wed, 25 Aug 2021 18:22:09 +0200 Subject: [PATCH 7/7] =?UTF-8?q?Suppresion=20des=20majuscules=20dans=20isVa?= =?UTF-8?q?lidateMember=20(qui=20devient=20isvalidatemember)=20pour=20evit?= =?UTF-8?q?er=20les=20probl=C3=A8mes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/models/resource-owner.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/resource-owner.entity.ts b/src/models/resource-owner.entity.ts index c135ed6..4284ef8 100644 --- a/src/models/resource-owner.entity.ts +++ b/src/models/resource-owner.entity.ts @@ -64,7 +64,7 @@ export class ResourceOwner { bdeId: string; @Column('boolean', { default: false }) - isValidateMember: boolean = false; + isvalidatemember: boolean = false; // Relationships