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/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/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


diff --git a/src/account/account.resolver.ts b/src/account/account.resolver.ts index 327129b..cedc411 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'; @@ -37,8 +37,94 @@ 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 + return this.bdeResolver.getBDEs() + .then(async bde => { + console.log(bde[0].id); + + const result = await this.accountService.createAccountToValidate(mail, firstN, lastN, bde[0].id); //Assume that there is only one BDE. + 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); + }); + //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); } + + /** + * Get all user who are not validate yet. + */ + @Query(() => [UserType], {name: 'allUsers'}) + 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 93a2d4d..baa7d26 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) { @@ -81,6 +81,40 @@ export class AccountService { return user; } + async createAccountToValidate ( + mail: string, + firstN: string, + lastN: string, + bdeId: string + ): Promise { + let local = true; //Disbale mail + + 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'); + } + + if ( ! local) { + try { + await this.mailingService.sendFirstPartRegistrationMail(user); + } catch {} + } + + return user; + } + async updateAccount( userId: string, userData: { @@ -130,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. @@ -152,20 +198,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-password 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( @@ -178,6 +255,7 @@ export class AccountService { password: newPassword, }, ); + this.logger.debug("The new password is now configured ..."); return { ok: true }; } @@ -208,6 +286,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 */ @@ -218,7 +297,7 @@ export class AccountService { }, }); - if (ro) { + if (ro && ro.isvalidatemember == true) { this.updateResetPasswordToken(ro); try { await this.resourceOwnerRepository.update(ro.id, { @@ -230,8 +309,62 @@ 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( + { + 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; + } } 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) { diff --git a/src/mailing/mailing.service.ts b/src/mailing/mailing.service.ts index 854492b..24ee2cb 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=${ @@ -82,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]`. diff --git a/src/models/resource-owner.entity.ts b/src/models/resource-owner.entity.ts index aac4e3e..4284ef8 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