diff --git a/packages/frontend/src/cleanup/Main.vue b/packages/frontend/src/cleanup/Main.vue index 8dfe00570..579e73f65 100644 --- a/packages/frontend/src/cleanup/Main.vue +++ b/packages/frontend/src/cleanup/Main.vue @@ -49,9 +49,9 @@ @@ -74,9 +74,7 @@ export default { SearchBar: () => import('@/cleanup/components/common/SearchBar'), GlobalToast: () => import('@/cleanup/components/common/GlobalToast'), GlobalLoading: () => import('@/cleanup/components/common/GlobalLoading'), - EmailVerificationBanner: () => { - return import('@/cleanup/components/user/EmailVerificationBanner') - } + EmailVerificationBanner: () => import('@/cleanup/components/user/EmailVerificationBanner') }, apollo: { serverInfo: { @@ -205,4 +203,7 @@ export default { opacity: 0.5; width: 0px; } +.email-banner { + z-index: 2; +} diff --git a/packages/server/modules/emails/rest/index.js b/packages/server/modules/emails/rest/index.js index 7be455ff1..91c880e78 100644 --- a/packages/server/modules/emails/rest/index.js +++ b/packages/server/modules/emails/rest/index.js @@ -3,7 +3,7 @@ const knex = require( `${appRoot}/db/knex` ) const { getUserByEmail } = require( `${appRoot}/modules/core/services/users` ) const { contextMiddleware } = require( `${appRoot}/modules/shared` ) -const { sendEmailVerification } = require( '../services/verification' ) +const { sendEmailVerification, isVerificationValid } = require( '../services/verification' ) const Verifications = () => knex( 'email_verifications' ) const Users = () => knex( 'users' ) @@ -35,6 +35,8 @@ module.exports = ( app ) => { return res.status( 200 ).send( 'Email verification initiated.' ) } catch ( error ) { + if ( error.message.includes( 'You already have a valid' ) ) + return res.status( 400 ).send( error.message ) return res.status( 500 ).send( error.message ) } }, @@ -51,8 +53,7 @@ module.exports = ( app ) => { return res.status( 404 ).send( 'No verification with this token.' ) } - const timeDiff = Math.abs( Date.now() - new Date( verification.createdAt ) ) - if ( timeDiff > 8.64e+7 ) { + if ( !isVerificationValid( verification ) ) { return res .status( 400 ) .send( 'Verification expired, please request a new one.' ) diff --git a/packages/server/modules/emails/services/verification.js b/packages/server/modules/emails/services/verification.js index c64286475..a58fdae41 100644 --- a/packages/server/modules/emails/services/verification.js +++ b/packages/server/modules/emails/services/verification.js @@ -12,6 +12,10 @@ const sendEmailVerification = async ( { recipient } ) => { // we need to validate email here, since we'll send it out, // even if technically there is no chance ATM that an incorrect addr comes in const serverInfo = await getServerInfo() + const existingVerifications = await Verifications() + .where( { 'email': recipient } ) + if ( existingVerifications.some( ver => isVerificationValid( ver ) ) ) + throw new Error( 'You already have a valid verification message, please check your inbox' ) const verificationId = await createEmailVerification( { 'email': recipient } ) const verificationLink = new URL( `auth/verifyemail?t=${verificationId}`, process.env.CANONICAL_URL, @@ -27,6 +31,11 @@ const sendEmailVerification = async ( { recipient } ) => { } ) } +const isVerificationValid = ( { createdAt } ) => { + const timeDiff = Math.abs( Date.now() - new Date( createdAt ) ) + return timeDiff < 8.64e+7 +} + const prepareMessage = async ( { verificationLink, serverInfo } ) => { const subject = `Speckle Server ${serverInfo.name} email verification` const text = ` @@ -90,4 +99,4 @@ const createEmailVerification = async ( { email } ) => { return verification.id } -module.exports = { sendEmailVerification } +module.exports = { sendEmailVerification, isVerificationValid } diff --git a/packages/server/modules/emails/tests/verifications.spec.js b/packages/server/modules/emails/tests/verifications.spec.js index ace6d48fc..b2e2404cc 100644 --- a/packages/server/modules/emails/tests/verifications.spec.js +++ b/packages/server/modules/emails/tests/verifications.spec.js @@ -68,6 +68,8 @@ describe( 'Email verifications @emails', () => { expect( sentResult.message ).to.contain( expectedVerificationUrl ) + await Verifications( ).where( { id: ver.id } ).del() + await request( expressApp ) .post( '/auth/emailverification/request' ) .send( { email: userA.email } ) @@ -87,6 +89,23 @@ describe( 'Email verifications @emails', () => { .set( 'Authorization', userB.token ) .expect( 403 ) } ) + it( 'Should not create a new verification while the previous is valid', async () => { + await request( expressApp ) + .post( '/auth/emailverification/request' ) + .send( { email: userA.email } ) + .set( 'Authorization', userA.token ) + .expect( 400 ) + } ) + it( 'Should create a new verification if the previous is invalid', async () => { + await Verifications().where( { email: userA.email } ) + .update( { createdAt: new Date( 0 ) } ) + + await request( expressApp ) + .post( '/auth/emailverification/request' ) + .send( { email: userA.email } ) + .set( 'Authorization', userA.token ) + .expect( 200 ) + } ) } ) describe( 'Use email verification', () => { it( 'Should not verify without a token', async () => { @@ -125,7 +144,7 @@ describe( 'Email verifications @emails', () => { expect( verifications ).to.have.lengthOf( 2 ) await request( expressApp ) - .get( `/auth/verifyemail?t=${verifications[0].id}` ) + .get( `/auth/verifyemail?t=${verifications[1].id}` ) .expect( 302 ) verifications = await Verifications().where( { email: userA.email } )