diff --git a/public_html/invalid_link.html b/public_html/invalid_link.html index 66bdc788..b19044e5 100644 --- a/public_html/invalid_link.html +++ b/public_html/invalid_link.html @@ -35,6 +35,8 @@ padding: 0 0 0 0; } + +

Invalid Link

diff --git a/public_html/invalid_verification_link.html b/public_html/invalid_verification_link.html new file mode 100644 index 00000000..fe6914fc --- /dev/null +++ b/public_html/invalid_verification_link.html @@ -0,0 +1,68 @@ + + + + + Invalid Link + + + + + +
+

Invalid Verification Link

+
+ + +
+
+ + diff --git a/public_html/link_send_fail.html b/public_html/link_send_fail.html new file mode 100644 index 00000000..7f817a2c --- /dev/null +++ b/public_html/link_send_fail.html @@ -0,0 +1,45 @@ + + + + + Invalid Link + + + + +
+

No link sent. User not found or email already verified

+
+ + diff --git a/public_html/link_send_success.html b/public_html/link_send_success.html new file mode 100644 index 00000000..55d9cad6 --- /dev/null +++ b/public_html/link_send_success.html @@ -0,0 +1,45 @@ + + + + + Invalid Link + + + + +
+

Link Sent! Check your email.

+
+ + diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js index 7e27239c..fad7f4ef 100644 --- a/spec/EmailVerificationToken.spec.js +++ b/spec/EmailVerificationToken.spec.js @@ -6,7 +6,7 @@ const Config = require('../src/Config'); describe("Email Verification Token Expiration: ", () => { - it('show the invalid link page, if the user clicks on the verify email link after the email verify token expires', done => { + it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', done => { var user = new Parse.User(); var sendEmailOptions; var emailAdapter = { @@ -37,7 +37,7 @@ describe("Email Verification Token Expiration: ", () => { followRedirect: false, }, (error, response) => { expect(response.statusCode).toEqual(302); - expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html'); + expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test'); done(); }); }, 1000); @@ -313,7 +313,7 @@ describe("Email Verification Token Expiration: ", () => { }); }); - it('clicking on the email verify link by an email VERIFIED user that was setup before enabling the expire email verify token should show an invalid link', done => { + it('clicking on the email verify link by an email VERIFIED user that was setup before enabling the expire email verify token should show email verify email success', done => { var user = new Parse.User(); var sendEmailOptions; var emailAdapter = { @@ -359,7 +359,7 @@ describe("Email Verification Token Expiration: ", () => { followRedirect: false, }, (error, response) => { expect(response.statusCode).toEqual(302); - expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html'); + expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=testEmailVerifyTokenValidity'); done(); }); }) @@ -369,7 +369,7 @@ describe("Email Verification Token Expiration: ", () => { }); }); - it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show an invalid link', done => { + it('clicking on the email verify link by an email UNVERIFIED user that was setup before enabling the expire email verify token should show invalid verficiation link page', done => { var user = new Parse.User(); var sendEmailOptions; var emailAdapter = { @@ -409,7 +409,7 @@ describe("Email Verification Token Expiration: ", () => { followRedirect: false, }, (error, response) => { expect(response.statusCode).toEqual(302); - expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html'); + expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=testEmailVerifyTokenValidity&appId=test'); done(); }); }) diff --git a/spec/ValidationAndPasswordsReset.spec.js b/spec/ValidationAndPasswordsReset.spec.js index e127fd03..d1d1007b 100644 --- a/spec/ValidationAndPasswordsReset.spec.js +++ b/spec/ValidationAndPasswordsReset.spec.js @@ -655,7 +655,7 @@ describe("Custom Pages, Email Verification, Password Reset", () => { }); }); - it('redirects you to invalid link if you try to validate a nonexistant users email', done => { + it('redirects you to invalid verification link page if you try to validate a nonexistant users email', done => { reconfigureServer({ appName: 'emailing app', verifyUserEmails: true, @@ -671,7 +671,32 @@ describe("Custom Pages, Email Verification, Password Reset", () => { followRedirect: false, }, (error, response) => { expect(response.statusCode).toEqual(302); - expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html'); + expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=sadfasga&appId=test'); + done(); + }); + }); + }); + + it('redirects you to link send fail page if you try to resend a link for a nonexistant user', done => { + reconfigureServer({ + appName: 'emailing app', + verifyUserEmails: true, + emailAdapter: { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {} + }, + publicServerURL: "http://localhost:8378/1" + }) + .then(() => { + request.post('http://localhost:8378/1/apps/test/resend_verification_email', { + followRedirect: false, + form: { + username: "sadfasga" + } + }, (error, response) => { + expect(response.statusCode).toEqual(302); + expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/link_send_fail.html'); done(); }); }); @@ -685,7 +710,7 @@ describe("Custom Pages, Email Verification, Password Reset", () => { followRedirect: false, }, (error, response) => { expect(response.statusCode).toEqual(302); - expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_link.html'); + expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/invalid_verification_link.html?username=zxcv&appId=test'); user.fetch() .then(() => { expect(user.get('emailVerified')).toEqual(false); diff --git a/src/Config.js b/src/Config.js index 8958a079..52d1bb81 100644 --- a/src/Config.js +++ b/src/Config.js @@ -234,6 +234,18 @@ export class Config { return this.customPages.invalidLink || `${this.publicServerURL}/apps/invalid_link.html`; } + get invalidVerificationLinkURL() { + return this.customPages.invalidVerificationLink || `${this.publicServerURL}/apps/invalid_verification_link.html`; + } + + get linkSendSuccessURL() { + return this.customPages.linkSendSuccess || `${this.publicServerURL}/apps/link_send_success.html` + } + + get linkSendFailURL() { + return this.customPages.linkSendFail || `${this.publicServerURL}/apps/link_send_fail.html` + } + get verifyEmailSuccessURL() { return this.customPages.verifyEmailSuccess || `${this.publicServerURL}/apps/verify_email_success.html`; } diff --git a/src/Controllers/UserController.js b/src/Controllers/UserController.js index b6ca02a5..305fdccf 100644 --- a/src/Controllers/UserController.js +++ b/src/Controllers/UserController.js @@ -60,11 +60,17 @@ export class UserController extends AdaptableController { updateFields._email_verify_token_expires_at = {__op: 'Delete'}; } - return this.config.database.update('_User', query, updateFields).then((document) => { - if (!document) { - throw undefined; + var checkIfAlreadyVerified = new RestQuery(this.config, Auth.master(this.config), '_User', {username: username, emailVerified: true}); + return checkIfAlreadyVerified.execute().then(result => { + if (result.results.length) { + return Promise.resolve(result.results.length[0]); } - return Promise.resolve(document); + return this.config.database.update('_User', query, updateFields).then((document) => { + if (!document) { + throw undefined + } + return Promise.resolve(document); + }) }); } @@ -134,6 +140,18 @@ export class UserController extends AdaptableController { }); } + resendVerificationEmail(username) { + return this.getUserIfNeeded({username: username}).then((aUser) => { + if (!aUser || aUser.emailVerified) { + throw undefined; + } + this.setEmailVerifyToken(aUser); + return this.config.database.update('_User', {username}, aUser).then(() => { + this.sendVerificationEmail(aUser); + }); + }); + } + setPasswordResetToken(email) { const token = { _perishable_token: randomString(25) }; diff --git a/src/Routers/PublicAPIRouter.js b/src/Routers/PublicAPIRouter.js index e3a569ff..012c13b3 100644 --- a/src/Routers/PublicAPIRouter.js +++ b/src/Routers/PublicAPIRouter.js @@ -31,7 +31,35 @@ export class PublicAPIRouter extends PromiseRouter { location: `${config.verifyEmailSuccessURL}?${params}` }); }, ()=> { + return this.invalidVerificationLink(req); + }) + } + + resendVerificationEmail(req) { + const username = req.body.username; + const appId = req.params.appId; + const config = new Config(appId); + + if (!config.publicServerURL) { + return this.missingPublicServerURL(); + } + + if (!username) { return this.invalidLink(req); + } + + const userController = config.userController; + + return userController.resendVerificationEmail(username).then(() => { + return Promise.resolve({ + status: 302, + location: `${config.linkSendSuccessURL}` + }); + }, ()=> { + return Promise.resolve({ + status: 302, + location: `${config.linkSendFailURL}` + }); }) } @@ -123,6 +151,19 @@ export class PublicAPIRouter extends PromiseRouter { }); } + invalidVerificationLink(req) { + const config = req.config; + if (req.query.username && req.params.appId) { + const params = qs.stringify({username: req.query.username, appId: req.params.appId}); + return Promise.resolve({ + status: 302, + location: `${config.invalidVerificationLinkURL}?${params}` + }); + } else { + return this.invalidLink(req); + } + } + missingPublicServerURL() { return Promise.resolve({ text: 'Not found.', @@ -140,6 +181,10 @@ export class PublicAPIRouter extends PromiseRouter { req => { this.setConfig(req) }, req => { return this.verifyEmail(req); }); + this.route('POST', '/apps/:appId/resend_verification_email', + req => { this.setConfig(req); }, + req => { return this.resendVerificationEmail(req); }); + this.route('GET','/apps/choose_password', req => { return this.changePassword(req); });