From 6375b361522e9f27b9c4fbd0b04a66da7e1655e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Dubigny?= Date: Fri, 28 Jul 2023 15:24:10 +0200 Subject: [PATCH 1/2] fix: notify all email send after peer authentication, not before --- src/controllers/organization.ts | 3 --- .../organization/authentication-by-peers.ts | 22 ------------------- src/middlewares/user.ts | 22 +++++++++++++------ 3 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/controllers/organization.ts b/src/controllers/organization.ts index 21e3c49b..d46edac4 100644 --- a/src/controllers/organization.ts +++ b/src/controllers/organization.ts @@ -24,7 +24,6 @@ import { getOrganizationSuggestions, joinOrganization, } from '../managers/organization/join'; -import { authenticateByPeers } from '../managers/organization/authentication-by-peers'; export const getJoinOrganizationController = async ( req: Request, @@ -108,8 +107,6 @@ export const postJoinOrganizationMiddleware = async ( user_id: req.session.user!.id, }); - await authenticateByPeers(userOrganizationLink); - if (req.session.mustReturnOneOrganizationInPayload) { await selectOrganization({ user_id: req.session.user!.id, diff --git a/src/managers/organization/authentication-by-peers.ts b/src/managers/organization/authentication-by-peers.ts index 97624277..69e61248 100644 --- a/src/managers/organization/authentication-by-peers.ts +++ b/src/managers/organization/authentication-by-peers.ts @@ -9,7 +9,6 @@ import { NotFoundError, UserAlreadyAskedForSponsorshipError, } from '../../errors'; -import { isEligibleToSponsorship } from '../../services/organization'; import { sendMail } from '../../connectors/sendinblue'; import { updateUserOrganizationLink } from '../../repositories/organization/setters'; import { getOrganizationsByUserId } from './main'; @@ -19,27 +18,6 @@ import { } from '../../repositories/moderation'; import { SUPPORT_EMAIL_ADDRESS } from '../../env'; -export const authenticateByPeers = async ( - link: UserOrganizationLink -): Promise<{ hasBeenAuthenticated: boolean }> => { - const { organization_id, user_id, is_external } = link; - const organizationUsers = await getUsers(organization_id); - const user = organizationUsers.find(({ id }) => id === user_id); - const organization = await findOrganizationById(organization_id); - - // The user should be in the organization already - if (isEmpty(user) || isEmpty(organization)) { - throw new NotFoundError(); - } - - if (isEligibleToSponsorship(organization)) { - return { hasBeenAuthenticated: false }; - } - - await notifyAllMembers(link); - - return { hasBeenAuthenticated: true }; -}; export const notifyAllMembers = async ({ organization_id, user_id, diff --git a/src/middlewares/user.ts b/src/middlewares/user.ts index c30ff4bc..15c06b5e 100644 --- a/src/middlewares/user.ts +++ b/src/middlewares/user.ts @@ -3,10 +3,13 @@ import { isEmpty } from 'lodash'; import { isUrlTrusted } from '../services/security'; import { updateEmailAddressVerificationStatus } from '../managers/user'; import { isEligibleToSponsorship } from '../services/organization'; -import { NotImplemented } from 'http-errors'; import { getOrganizationsByUserId } from '../managers/organization/main'; -import { greetForJoiningOrganization } from '../managers/organization/authentication-by-peers'; +import { + greetForJoiningOrganization, + notifyAllMembers, +} from '../managers/organization/authentication-by-peers'; import { getSelectedOrganizationId } from '../repositories/redis/selected-organization'; +import { getUserOrganizationLink } from '../repositories/organization/getters'; // redirect user to start sign in page if no email is available in session export const checkEmailInSessionMiddleware = async ( @@ -227,15 +230,20 @@ export const checkUserHasBeenAuthenticatedByPeersMiddleware = ( if (!isEmpty(organizationThatNeedsAuthenticationByPeers)) { if ( - !isEligibleToSponsorship(organizationThatNeedsAuthenticationByPeers) + isEligibleToSponsorship(organizationThatNeedsAuthenticationByPeers) ) { - // this should never happen as all members are notified by default - return next(new NotImplemented()); + return res.redirect( + `/users/choose-sponsor/${organizationThatNeedsAuthenticationByPeers.id}` + ); } - return res.redirect( - `/users/choose-sponsor/${organizationThatNeedsAuthenticationByPeers.id}` + const link = await getUserOrganizationLink( + organizationThatNeedsAuthenticationByPeers.id, + req.session.user!.id ); + + // link exists because we get the organization id from getOrganizationsByUserId above + await notifyAllMembers(link!); } return next(); From 5c54e01ec9f7d801e3fcbf5743285b5464432683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Dubigny?= Date: Fri, 28 Jul 2023 16:20:28 +0200 Subject: [PATCH 2/2] tests: add tests to ensure join-organization email is send after account validation --- cypress/e2e/join_org_with_verified_domain.cy.js | 2 +- ...with_code_sent_to_official_contact_email.cy.js | 15 +++++++++++++++ ...n_with_code_sent_to_official_contact_email.sql | 14 +++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/join_org_with_verified_domain.cy.js b/cypress/e2e/join_org_with_verified_domain.cy.js index 05fafcf2..788d8abf 100644 --- a/cypress/e2e/join_org_with_verified_domain.cy.js +++ b/cypress/e2e/join_org_with_verified_domain.cy.js @@ -95,7 +95,7 @@ describe('join organizations', () => { // assert reception of notification email .then(email => { expect(email.body).to.match( - /.*Jean Nouveau.*\(c6c64542-5601-43e0-b320-b20da72f6edc@mailslurp.com\) a rejoint votre organisation.*Commune de clamart - Mairie.*sur .*MonComptePro/ + /.*Jean Nouveau.*\(c6c64542-5601-43e0-b320-b20da72f6edc@mailslurp\.com\) a rejoint votre organisation.*Commune de clamart - Mairie.*sur .*MonComptePro/ ); }); diff --git a/cypress/e2e/join_with_code_sent_to_official_contact_email.cy.js b/cypress/e2e/join_with_code_sent_to_official_contact_email.cy.js index d4b2dcab..4bc6138f 100644 --- a/cypress/e2e/join_with_code_sent_to_official_contact_email.cy.js +++ b/cypress/e2e/join_with_code_sent_to_official_contact_email.cy.js @@ -57,5 +57,20 @@ describe('join organizations', () => { }); cy.contains('Votre compte est créé'); + + cy.mailslurp() + .then(mailslurp => + mailslurp.waitForLatestEmail( + '26ccc0fa-0dc3-4f12-9335-7bb00282920c', + 60000, + true + ) + ) + // assert reception of notification email + .then(email => { + expect(email.body).to.match( + /.*Jean Nouveau.*\(c348a2c3-bf54-4f15-bb12-a2d7047c832f@mailslurp\.com\) a rejoint votre organisation.*Commune de lamalou-les-bains - Mairie.*sur .*MonComptePro/ + ); + }); }); }); diff --git a/cypress/fixtures/join_with_code_sent_to_official_contact_email.sql b/cypress/fixtures/join_with_code_sent_to_official_contact_email.sql index 8cd7408c..f16d2ec4 100644 --- a/cypress/fixtures/join_with_code_sent_to_official_contact_email.sql +++ b/cypress/fixtures/join_with_code_sent_to_official_contact_email.sql @@ -2,4 +2,16 @@ INSERT INTO users (id, email, email_verified, email_verified_at, encrypted_password, created_at, updated_at, given_name, family_name, phone_number, job) VALUES - (1, 'c348a2c3-bf54-4f15-bb12-a2d7047c832f@mailslurp.com', true, CURRENT_TIMESTAMP, '$2a$10$kzY3LINL6..50Fy9shWCcuNlRfYq0ft5lS.KCcJ5PzrhlWfKK4NIO', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'Jean', 'Nouveau', '0123456789', 'Sbire'); + (1, '26ccc0fa-0dc3-4f12-9335-7bb00282920c@mailslurp.com', true, CURRENT_TIMESTAMP, '$2a$10$kzY3LINL6..50Fy9shWCcuNlRfYq0ft5lS.KCcJ5PzrhlWfKK4NIO', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'Jean', 'Officiel', '0123456789', 'Sbire'), + (2, 'c348a2c3-bf54-4f15-bb12-a2d7047c832f@mailslurp.com', true, CURRENT_TIMESTAMP, '$2a$10$kzY3LINL6..50Fy9shWCcuNlRfYq0ft5lS.KCcJ5PzrhlWfKK4NIO', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'Jean', 'Nouveau', '0123456789', 'Sbire'); + + +INSERT INTO organizations + (id, siret, verified_email_domains, authorized_email_domains, created_at, updated_at) +VALUES + (1, '21340126800130', '{}', '{}', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); + +INSERT INTO users_organizations + (user_id, organization_id, is_external, verification_type, authentication_by_peers_type, has_been_greeted) +VALUES + (1, 1, false, null, 'all_members_notified', true)